import "package:flutter/material.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";

import "package:inventree/api.dart";
import "package:inventree/helpers.dart";
import "package:inventree/l10.dart";

import "package:inventree/barcode/tones.dart";

import "package:inventree/inventree/sentry.dart";

import "package:inventree/widget/dialogs.dart";
import "package:inventree/widget/snacks.dart";

/* Generic class which "handles" a barcode, by communicating with the InvenTree server,
 * and handling match / unknown / error cases.
 *
 * Override functionality of this class to perform custom actions,
 * based on the response returned from the InvenTree server
 */
class BarcodeHandler {
  BarcodeHandler();

  // Return the text to display on the barcode overlay
  // Note: Will be overridden by child classes
  String getOverlayText(BuildContext context) => "Barcode Overlay";

  // Called when the server "matches" a barcode
  Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
    // Override this function
  }

  // Called when the server does not know about a barcode
  Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
    // Override this function

    barcodeFailureTone();

    showSnackIcon(
      (data["error"] ?? L10().barcodeNoMatch) as String,
      success: false,
      icon: Icons.qr_code,
    );
  }

  // Called when the server returns an unhandled response
  Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async {
    barcodeFailureTone();
    showServerError("barcode/", L10().responseUnknown, data.toString());
  }

  /*
    * Base function to capture and process barcode data.
    *
    * Returns true only if the barcode scanner should remain open
    */
  Future<void> processBarcode(
    String barcode, {
    String url = "barcode/",
    Map<String, dynamic> extra_data = const {},
  }) async {
    debug("Scanned barcode data: '${barcode}'");

    barcode = barcode.trim();

    // Empty barcode is invalid
    if (barcode.isEmpty) {
      barcodeFailureTone();

      showSnackIcon(
        L10().barcodeError,
        icon: TablerIcons.exclamation_circle,
        success: false,
      );

      return;
    }

    APIResponse? response;

    try {
      response = await InvenTreeAPI().post(
        url,
        body: {"barcode": barcode, ...extra_data},
        expectedStatusCode: null, // Do not show an error on "unexpected code"
      );
    } catch (error, stackTrace) {
      sentryReportError("Barcode.processBarcode", error, stackTrace);
      response = null;
    }

    if (response == null) {
      barcodeFailureTone();
      showSnackIcon(L10().barcodeError, success: false);
      return;
    }

    debug("Barcode scan response" + response.data.toString());

    Map<String, dynamic> data = response.asMap();

    // Handle strange response from the server
    if (!response.isValid() || !response.isMap()) {
      await onBarcodeUnknown({});

      showSnackIcon(L10().serverError, success: false);

      // We want to know about this one!
      await sentryReportMessage(
        "BarcodeHandler.processBarcode returned unexpected value",
        context: {
          "data": response.data?.toString() ?? "null",
          "barcode": barcode,
          "url": url,
          "statusCode": response.statusCode.toString(),
          "valid": response.isValid().toString(),
          "error": response.error,
          "errorDetail": response.errorDetail,
          "className": "${this}",
        },
      );
    } else if (data.containsKey("success")) {
      await onBarcodeMatched(data);
    } else if ((response.statusCode >= 400) || data.containsKey("error")) {
      await onBarcodeUnknown(data);
    } else {
      await onBarcodeUnhandled(data);
    }
  }
}
