<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { onMounted, computed, ref } from "vue";
import {
  reset_all_values,
  customer_name,
  nextStep,
  searchAndSelectCustomer,
  isCustomerSelected,
  order_id,
  customer_id,
  step,
  reg_1,
  reg_2,
  reg_3,
  reference,
  order_notes,
  order_safety_seal,
  loadCustomerAttributes,
  hasAttribute,
  uploadAttachment,
  restoreStoredPosOrderId,
} from "@/components/shop/POSDepartmentProcess.vue";
import * as POSDepartmentProcess from "@/components/shop/POSDepartmentProcess.vue";
import { errors } from "@/components/request/HandleGlobalError.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import ContinueArrow from "@/components/viewport/elements/icons/ContinueArrow.vue";
import {
  vehicles,
  metadata,
  getCustomerId,
  popups,
  resetPos,
  attachments,
  transactionHistory,
  transactionItems,
} from "../objects/PosDepartmentStepMobileFlow.vue";
import {
  finalizeCurrentMobileOrder,
  getResolvedMobileSafetySeal,
  syncMobileSafetySealState,
} from "../objects/mobileOrderCompletion.js";
import Swal from "sweetalert2";
import LongPressListener from "@/components/viewport/elements/wrappers/LongPressListener.vue";
import { views } from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";

const { t } = useI18n();
const selectCustomerString = `${
  SessionUser.objects.global.language.select
} ${SessionUser.objects.global.language.customer.toLowerCase()}`;
const isCreatingOrder = computed(() => POSDepartmentProcess.isCreatingOrder?.value ?? false);

const props = defineProps({
  // If true, the button will have a white background. Default is true.
  isWhite: {
    type: Boolean,
    default: true,
  },
  isDark: {
    type: Boolean,
    default: false, // When set to true, this overrides the isWhite prop.
  },
  // Function that returns a Promise, to be executed before proceeding to the next step.
  onBeforeStep: {
    type: Function,
    default: () => Promise.resolve(),
  },
  // Custom action will override the default onClick behavior, if provided.
  customAction: {
    type: Function,
    default: null,
  },
  // Custom is the button disabled state. If provided, this will override the default disabled logic.
  customDisabled: {
    type: Boolean,
    default: null,
  },
  // Custom long press
  customLongPress: {
    type: Function,
    default: null,
  },
  // Button classes
  buttonClasses: {
    type: Array as () => string[],
    default: () => [],
  },
  actionKey: {
    type: String,
    default: null,
  },
  copyKey: {
    type: String,
    default: null,
  },
});

const updateReference = () => {
  if (!!order_id.value && order_id.value > 0) SessionUser.objects.orders.set.reference(order_id.value, reference.value);
};

const updateNotes = () => {
  if (!!order_id.value && order_id.value > 0) SessionUser.objects.orders.set.notes(order_id.value, order_notes.value);
};

const updateSafetySeal = () => {
  if (!!order_id.value && order_id.value > 0)
    SessionUser.objects.orders.set.safety_seal(order_id.value, order_safety_seal.value);
};

// Helpers: extracted for clarity and reuse
const isNonEmptyString = (val) => typeof val === "string" && val.trim().length > 0;

const resolveReferenceFromSources = () => {
  // 1) Metadata reference
  const metaRef = metadata.getReference?.();
  if (isNonEmptyString(metaRef)) return metaRef.trim();

  // 2) Local reactive reference field
  if (isNonEmptyString(reference.value)) return reference.value.trim();

  // 3) Vehicle #1 reference (fallback)
  const vehicleRef = vehicles.get(1)?.reference;
  if (isNonEmptyString(vehicleRef)) return String(vehicleRef).trim();

  return null;
};

const promptForReference = async (initialValue = "") => {
  const result = await Swal.fire({
    title: t("admin.pos.reference_required_title"),
    text: t("admin.pos.reference_required_text"),
    input: "text",
    inputLabel: t("common.reference"),
    inputValue: initialValue,
    showCancelButton: true,
    confirmButtonText: t("admin.pos.confirm"),
    cancelButtonText: t("admin.pos.cancel"),
    preConfirm: (newReference) => {
      if (!isNonEmptyString(newReference)) {
        Swal.showValidationMessage(t("admin.pos.reference_cannot_be_empty"));
      }
      return newReference;
    },
  });
  if (result.isConfirmed && isNonEmptyString(result.value)) {
    return result.value.trim();
  }
  return null;
};

// Refactored main function
const validateReferenceRequirements = async () => {
  await loadCustomerAttributes();

  const customerRequiresReference = hasAttribute("requiresReferenceNumber");

  // Try to resolve an existing reference from known sources
  const resolvedRef = resolveReferenceFromSources();
  if (isNonEmptyString(resolvedRef)) {
    // Ensure both local ref and metadata are synchronized
    if (reference.value !== resolvedRef) reference.value = resolvedRef;
    if (metadata.getReference() !== resolvedRef) metadata.setReference(resolvedRef);
    return true;
  }

  // If customer does not require a reference, we are good even if none was resolved
  if (!customerRequiresReference) {
    return true;
  }

  // Prompt user for a reference if required and none available
  try {
    const enteredRef = await promptForReference(reference.value ?? "");
    if (isNonEmptyString(enteredRef)) {
      reference.value = enteredRef;
      metadata.setReference(enteredRef);
      console.warn("New reference set:", enteredRef);
      return true;
    }
    console.warn("Reference number is required but was not provided. Aborting operation.");
    return false;
  } catch (error) {
    console.warn("An error occurred while setting the reference number:", error);
    return false;
  }
};

const step1 = async () => {
  /** This function can be used to perform any specific actions for step 1 */
  // Set registration numbers
  reg_1.value = vehicles.vehicle_1.value?.reg || "";
  reg_2.value = vehicles.vehicle_2.value?.reg || "";
  reg_3.value = vehicles.vehicle_3.value?.reg || "";
  // Reset the vehicle selection
  views.vehicleSelection.value = false;
  // Merge references and notes
  mergeReferences();
  mergeNotes();
  mergeSafetySeal();
  // We only need to check if the customer requires reference, if the field is empty.
  if (!isNonEmptyString(reference.value)) {
    const isValid = await validateReferenceRequirements();
    if (!isValid) {
      return false;
    }
  }

  // Reference is resolved, persist metadata and pending attachments.
  return finalizeStep1();
};
const mergeReferences = () => {
  if (metadata.getReference() && metadata.getReference().length >= 1) {
    // If there is no reference, set it to the registration number of vehicle 1
    reference.value = metadata.getReference();
    updateReference();
  } else if (reference.value && reference.value.length >= 1) {
    // If there is a reference in the field, set it to the metadata
    metadata.setReference(reference.value);
    updateReference();
  } else if (vehicles.vehicle_1.value?.reference && vehicles.vehicle_1.value?.reference.length >= 1) {
    // If there is no reference, set it to the registration number of vehicle 1
    reference.value = vehicles.vehicle_1.value?.reference;
    metadata.setReference(reference.value);
    updateReference();
  }
};
const mergeNotes = () => {
  if (metadata.getNotes() && metadata.getNotes().length >= 1) {
    order_notes.value = metadata.getNotes();
    updateNotes();
  } else if (order_notes.value && order_notes.value.length >= 1) {
    metadata.setNotes(order_notes.value);
    updateNotes();
  }
};
const mergeSafetySeal = () => {
  const resolvedSafetySeal = getResolvedMobileSafetySeal();
  order_safety_seal.value = resolvedSafetySeal;
  syncMobileSafetySealState(resolvedSafetySeal);
  updateSafetySeal();
};
const finalizeStep1 = async () => {
  mergeReferences();
  mergeNotes();
  mergeSafetySeal();
  // Upload the attachments if there are any (And the order id has been created)
  if (attachments.getBase64().length > 0 && order_id.value > 0) {
    const uploadResults = await Promise.all(
      attachments.getBase64().map(async (attachment: any) => {
        try {
          const didUpload = await uploadAttachment(attachment);
          if (didUpload) {
            console.warn("Attachment uploaded successfully");
            attachments.removeBase64(attachment);
          }
          return didUpload;
        } catch (error: any) {
          errors.value.push(error);
          console.warn("An error occurred while uploading the attachment:", error);
          return false;
        }
      })
    );

    if (uploadResults.some((didUpload) => didUpload === false)) {
      console.warn("One or more attachments could not be uploaded before completion.");
    }
  }
  return true;
};

const addTransactionToHistory = async (orderId: number) => {
  SessionUser.objects.orders.get
    .single(orderId)
    .then((transaction: any) => {
      if (transaction) {
        transactionHistory.add(transaction);
        console.warn("Transaction added to history:", transaction);
      } else {
        console.warn("Unable to add transaction to history: Order not found.");
      }
    })
    .catch((error: any) => {
      errors.value.push(error);
      console.warn("An error occurred while fetching the transaction for history:", error);
    });
};

const toPositiveInteger = (value: any) => {
  const parsedValue = Number.parseInt(String(value ?? ""), 10);
  return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : null;
};

const getSelectedBookingId = () => {
  return toPositiveInteger(metadata.getBookingId?.() ?? metadata.bookingId?.value ?? null);
};

const completeStep2Order = async ({
  bookingSafetySeal = null,
  markOrderCompleted = false,
}: {
  bookingSafetySeal?: string | null;
  markOrderCompleted?: boolean;
} = {}) => {
  await finalizeCurrentMobileOrder({
    bookingSafetySeal,
    markOrderCompleted,
  });
  popups.select("completed_transaction", {
    message: `Order #${order_id.value} successfully created.`,
  });
  completeOrder();
};

const step2 = async () => {
  /** This function can be used to perform any specific actions for step 2 */
  // If the customer is paying with a card, go to the payment step
  if (isCustomerSelected() && getCustomerId() === 999) {
    nextStep({ isMobile: true });
    return;
  }

  const selectedBookingId = getSelectedBookingId();
  const hasWashCertificateInBasket = Boolean(transactionItems.containsWashCertificate?.());
  const resolvedSafetySeal = getResolvedMobileSafetySeal();
  const hasResolvedSafetySeal = isNonEmptyString(resolvedSafetySeal);
  const requiresBookingCompletionPopup = Boolean(selectedBookingId && hasWashCertificateInBasket);
  const shouldMarkOrderAsCompleted = !selectedBookingId && hasWashCertificateInBasket;

  try {
    if (requiresBookingCompletionPopup && hasResolvedSafetySeal) {
      syncMobileSafetySealState(resolvedSafetySeal);
      await completeStep2Order({
        bookingSafetySeal: resolvedSafetySeal,
        markOrderCompleted: false,
      });
      return;
    }

    if (requiresBookingCompletionPopup) {
      const onCompleteBookingWithSafetySeal = async ({ safetySeal = "" } = {}) => {
        syncMobileSafetySealState(safetySeal);
        await completeStep2Order({
          bookingSafetySeal: String(safetySeal ?? ""),
          markOrderCompleted: false,
        });
      };

      popups.select("complete_booking", {
        props: {
          safetySeal: getResolvedMobileSafetySeal(),
          onCompleteWithCertificate: onCompleteBookingWithSafetySeal,
          onCompleteWithoutCertificate: onCompleteBookingWithSafetySeal,
        },
      });
      return;
    }

    await completeStep2Order({
      markOrderCompleted: shouldMarkOrderAsCompleted,
    });
  } catch (error: any) {
    errors.value.push(error);
    console.warn("An error occurred while completing the mobile order:", error);
    popups.select("error");
  }
};

const completeOrder = () => {
  addTransactionToHistory(order_id.value);
  setTimeout(() => {
    // If the customer is not paying with a card, reset the POS state (After 3 seconds, to make sure the popup is visible)
    reset_all_values(); // Reset all values
    order_id.value = 0; // Reset the order ID
    popups.clear(); // Clear the popup
    resetPos(); // Reset the POS state
    // Start the process again
  }, 3000);
};

const step3 = () => {
  /** This function can be used to perform any specific actions for step 3 */
  // This step is not defined, but you can add your logic here if needed
  console.warn("Step 3: Card payment step is not defined.");
};

const isProcessingClick = ref(false);
const isBusy = computed(() => metadata.getLoadingState() || isCreatingOrder.value || isProcessingClick.value);

const onClick = async () => {
  // Check if the click is cancelled
  if (isClickCancelled.value || isProcessingClick.value) {
    return;
  }
  // Check if a custom action is provided
  if (props.customAction) {
    isProcessingClick.value = true;
    try {
      await Promise.resolve(props.customAction());
    } finally {
      isProcessingClick.value = false;
    }
    return;
  }
  if (!(isCustomerSelected() && getCustomerId() > 0)) {
    // Show the popup to select a customer
    popups.select("select_customer");
    return;
  }

  isProcessingClick.value = true;
  try {
    await props.onBeforeStep();
    // Proceed to the next step
    switch (step.value) {
      case 1:
        if (!(await step1())) {
          return;
        }
        const restoredOrderId = await restoreStoredPosOrderId({
          validateOrder: true,
          customerId: getCustomerId(),
          departmentId: POSDepartmentProcess.getDepartment(),
          allowCompleted: false,
          syncDepartment: true,
        });
        if (restoredOrderId) {
          console.warn("Order ID retrieved from local storage:", restoredOrderId);
          nextStep({
            isMobile: true,
            orderCreation: false,
            bookingId: metadata.getBookingId?.() ?? null,
          });
          return;
        }
        nextStep({
          isMobile: true,
          orderCreation: true,
          bookingId: metadata.getBookingId?.() ?? null,
        });
        break;
      case 2:
        if (!(await step1())) {
          return;
        }
        await step2();
        break;
      case 3:
        // This step is not defined, but you can add your logic here if needed
        step3();
        break;
      default:
        break;
    }
  } catch (error) {
    console.warn("Next-step action was interrupted:", error);
  } finally {
    isProcessingClick.value = false;
  }
};

/** Synchronize customer selection state */
const applyCustomerSelection = () => {
  let shouldApplyCustomerSelection = false;
  if (!isCustomerSelected() && getCustomerId() > 0) {
    // Search and select the customer
    shouldApplyCustomerSelection = true;
  }
  if (isCustomerSelected() && parseInt(customer_id.value) !== getCustomerId()) {
    // The customers are out of sync
    shouldApplyCustomerSelection = true;
  }
  // If any checks failed, apply the customer selection.
  if (shouldApplyCustomerSelection) {
    searchAndSelectCustomer(getCustomerId());
  }
};

// Check customer selection states.
onMounted(() => {
  applyCustomerSelection();
});

const isRegistrationNumberFilled = () => {
  return vehicles?.vehicle_1?.value?.reg && vehicles.vehicle_1.value?.reg.length >= 4;
};
// Is the requirements for clicking the button met?
const isRequirementsForClickMet = () => {
  // If a custom disabled state is provided, use that.
  if (props.customDisabled !== null) {
    return !props.customDisabled;
  }
  /**
   * States, and their requirements:
   * If any popup is open, the button cannot be clicked.
   * If the metadata state is loading, the button cannot be clicked.
   * If the customer is not selected, the button can be clicked to open the customer selection popup.
   * If the customer is selected, the button can be clicked if the reg_1 is filled. (>= 4 characters)
   */
  // If any popup is open, the button cannot be clicked.
  if (popups.isSet.value) {
    return false;
  }
  // Prevent duplicate click handling while one action is in-flight.
  if (isBusy.value) {
    return false;
  }
  if (!isCustomerSelected()) {
    return true; // The customer is not selected, so the button can be clicked to open the customer selection popup.
  } else {
    // The customer is selected, so check if the reg_1 is filled.
    return isRegistrationNumberFilled();
  }
};

// Should the button be visible?
const isVisible = computed(() => {
  /**
   * No popups are open.
   * The step is 1, 2 or 3.
   */
  return !popups.isSet.value && (step.value === 1 || step.value === 2 || step.value === 3);
});

const isClickCancelled = ref(false);
const cancelOnClick = () => {
  isClickCancelled.value = true;
  setTimeout(() => {
    isClickCancelled.value = false;
  }, 100); // Reset after 100ms
};

const onLongPress = () => {
  // Cancel the onClick event (since it's a long press)
  cancelOnClick();
  // Check if there's a custom long press handler
  if (props.customLongPress) {
    props.customLongPress();
    return;
  }
  // If no custom long press handler is provided, proceed with the default behavior
  defaultLongPressBehavior();
};

const defaultLongPressBehavior = () => {
  // Select the customer
  popups.select("select_customer");
};

/**
 * Colors:
 * Primary:
 * :buttonClasses="['has-background-primary', 'has-text-black']"
 * Secondary:
 * :buttonClasses="['has-background-primary-dark', 'has-text-black']"
 */
</script>

<template>
  <LongPressListener @long-press="onLongPress">
    <GenericButton
      @click="onClick"
      :class="{
        white: props.isWhite,
        'has-background-black': props.isDark,
        'is-loading': isBusy,
        ...props.buttonClasses.reduce((acc, curr) => ({ ...acc, [curr]: true }), {}),
      }"
      data-testid="pos-mobile-next-step"
      :action-key="props.actionKey"
      :copy-key="props.copyKey"
      class="is-size-6-mobile is-size-5-tablet is-size-4-desktop"
      :disabled="!isRequirementsForClickMet()"
      v-show="isVisible"
    >
      <!-- Default content if no slot is provided -->
      <template v-if="!$slots.default">
        <template v-if="isCustomerSelected()">
          <span class="pos-mobile-cta-content">
            <span class="pos-mobile-cta-label">{{ customer_name }}</span>
            <span class="pos-mobile-cta-value">
              <ContinueArrow style="width: 100%; height: 100%; padding: 6px 12px; gap: 5px" />
            </span>
          </span>
        </template>
        <template v-else>
          <span>{{ selectCustomerString }}</span>
        </template>
      </template>
      <!-- Default slot for custom content -->
      <template v-else>
        <!-- {{ transactionItems.primaryItem.value?.addons?.length }} -->
        <slot></slot>
      </template>
    </GenericButton>
  </LongPressListener>
</template>

<style scoped>
.pos-mobile-cta-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  min-width: 0;
}

.pos-mobile-cta-label {
  min-width: 0;
  flex: 1;
  display: -webkit-box;
  text-align: left;
  line-height: 1.2;
  overflow: hidden;
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.pos-mobile-cta-value {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  flex-shrink: 0;
}

.has-text-black > :not(.icon) {
  color: black !important;
}

:deep(.pos-mobile-cta-content) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  min-width: 0;
}

:deep(.pos-mobile-cta-label) {
  min-width: 0;
  flex: 1;
  display: -webkit-box;
  text-align: left;
  line-height: 1.2;
  overflow: hidden;
  overflow-wrap: anywhere;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

:deep(.pos-mobile-cta-value) {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  flex-shrink: 0;
}

:deep(.pos-mobile-action-content) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  min-width: 0;
  min-height: 100%;
  box-sizing: border-box;
  padding-inline: 0.75rem;
}

:deep(.pos-mobile-action-label) {
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
  line-height: 1;
}

:deep(.pos-mobile-action-icon) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  margin-left: auto;
  min-width: 1.25rem;
  line-height: 1;
}

:deep(.pos-mobile-action-icon i) {
  display: block;
  line-height: 1;
}

:deep(.generic-button__content) {
  width: 100%;
  min-width: 0;
}
/* Loading state */
.is-loading {
  pointer-events: none;
  cursor: not-allowed;
}
.is-loading > *:not(.icon) {
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.6s ease-in-out;
}
/* Loading animation for the button */
.is-loading::after {
  transition: opacity 0.6s ease-in-out;
  content: "";
  position: absolute;
  top: auto;
  left: 50%;
  width: 1em;
  height: 1em;
  margin-top: -0.5em;
  margin-left: -0.5em;
  /* Set the color to the original text color */
  border: 0.2em solid white;
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
  display: inline-block;
  vertical-align: top;
  border-right-color: transparent;
  border-bottom-color: transparent;
  pointer-events: none;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
</style>
