import {
  AppointmentStatus,
  Document,
  EntityAttributeType,
  EntityCriteriaTriggerType,
  EntityRuleCriteriaType,
  EntityRuleResultType,
  Fixture,
  PaymentDetail,
  PaymentMethod,
  PaymentSetting,
  Property,
  PropertyEntity,
  PropertyEntityRenderDimensions,
  PropertyEntityType,
  deepClone,
  generateId,
  safePrice
} from "./chunk-N36JE2BE.js";
import {
  BehaviorSubject,
  EMPTY,
  HttpClient,
  Injectable,
  Observable,
  Subject,
  catchError,
  from,
  mergeMap,
  setClassMetadata,
  tap,
  toArray,
  ɵɵdefineInjectable,
  ɵɵinject
} from "./chunk-VAMMRICA.js";
import {
  __async
} from "./chunk-RVA4KI76.js";

// src/app/shared/opportunities/models/pricing.ts
var OpportunityPrice = class {
  calcTotalDiscount() {
    let totalDiscount = 0;
    this.appliedPromotions.forEach((promotion) => {
      if (promotion.appliedDiscount != null) {
        totalDiscount = totalDiscount + promotion.appliedDiscount;
      }
    });
    this.totalDiscount = totalDiscount;
  }
  constructor(price, details = [], customDetails = void 0, appliedPromotions = [], finalPrice = 0, targetPrice = 0, ablPrice = 0, parPrice = 0, targetPromoPrice = 0) {
    this.iso4217 = "USD";
    this.parPrice = 0;
    this.targetPrice = 0;
    this.targetPromoPrice = 0;
    this.ablPrice = 0;
    this.finalPrice = 0;
    this.appliedPromotions = [];
    this.totalDiscount = 0;
    if (price != null) {
      this.details = price.details;
      this.appliedPromotions = price.appliedPromotions;
      this.customItems = price.customItems ?? [];
      this.finalPrice = price.finalPrice;
      this.targetPrice = price.targetPrice;
      this.ablPrice = price.ablPrice;
      this.parPrice = price.parPrice;
      this.targetPromoPrice = price.targetPromoPrice;
    } else {
      this.details = details;
      this.appliedPromotions = appliedPromotions;
      this.customItems = customDetails ?? [];
      this.finalPrice = finalPrice;
      this.targetPrice = targetPrice;
      this.ablPrice = ablPrice;
      this.parPrice = parPrice;
      this.targetPromoPrice = targetPromoPrice;
    }
    this.calcTotalDiscount();
  }
  toJSON() {
    return {
      iso4217: this.iso4217,
      details: this.details,
      customItems: this.customItems,
      parPrice: this.parPrice,
      targetPrice: this.targetPrice,
      targetPromoPrice: this.targetPromoPrice,
      ablPrice: this.ablPrice,
      finalPrice: this.finalPrice,
      appliedPromotions: this.appliedPromotions,
      totalDiscount: this.totalDiscount
    };
  }
};
var OpportunityLineItem = class {
  constructor(entity = void 0, skuId = "", skuCategory = "", customerFacing = false, isAttachment = false, name = "", description = "", price = 0, quantity = 0) {
    this.entity = void 0;
    this.entity = entity;
    this.entityId = entity?.id;
    this.skuId = skuId;
    this.skuCategory = skuCategory;
    this.customerFacing = customerFacing;
    this.isAttachment = isAttachment;
    this.name = name;
    this.description = description;
    this.price = price;
    this.quantity = quantity;
  }
  toJSON() {
    return {
      entityId: this.entityId,
      skuId: this.skuId,
      skuCategory: this.skuCategory,
      customerFacing: this.customerFacing,
      isAttachment: this.isAttachment,
      name: this.name,
      description: this.description,
      price: this.price,
      quantity: this.quantity
    };
  }
};

// src/app/shared/opportunities/models/documents.ts
var OpportunityDocument = class extends Document {
  constructor(id, name, customerFacing, shopFacing, standalone, dynamic, alwaysShow, displayInSigning = true, mapping, displayOrder = 0, content = void 0, executed = false, signers = [], rules = [], documents = [], signatures = [], userContent = [], lastUpdated_Utc = void 0, created_Utc = void 0, generatedContent = void 0, originalHash = void 0) {
    super(id, name, customerFacing, shopFacing, standalone, dynamic, alwaysShow, displayInSigning, mapping, displayOrder, content, executed, signers, rules, documents, signatures, userContent, lastUpdated_Utc, created_Utc);
    this.documents = [];
    this.validForExecution = false;
    this.validForSigner = false;
    this.uiDirection = "";
    this.rendered = false;
    this.checkedValidation = false;
    this.generatedContent = generatedContent;
    this.documents = documents;
    this.originalHash = originalHash;
  }
};

// src/app/shared/opportunities/models/needs.ts
var CustomerNeedQuestion = class {
  constructor(question, possibleAnswers) {
    this.possibleAnswers = [];
    this.answers = [];
    this.storing = false;
    this.question = question;
    this.possibleAnswers = possibleAnswers;
  }
};
var CustomerNeedPossibleAnswer = class {
  constructor(title, answer) {
    this.title = title;
    this.answer = answer;
  }
};

// src/app/shared/opportunities/models/opportunity.ts
var Opportunity = class {
  constructor() {
    this.id = "";
    this.opportunityTypes = [];
    this.opportunityPlumbingIssues = [];
    this.isEmergency = false;
    this.status = OpportunityStatus.Open;
    this.availablePromotions = [];
    this.availableFixtures = [];
    this.availableEntities = [];
    this.availableSkus = [];
    this.availablePaymentSettings = [];
    this.availableDocuments = [];
    this.availableDispositionStatuses = [];
    this.availableShops = [];
    this.created_Utc = /* @__PURE__ */ new Date();
    this.ledger = [];
    this.accepted = false;
    this.customerNeeds = {};
    this.payments = [];
    this.lenders = [];
    this.documents = [];
    this.adminOverride = false;
    this.storing = false;
    this.syncing = false;
    this.reverting = false;
    this.accepting = false;
    this.priceChanged$ = new Subject();
    this.customPriceChanged$ = new Subject();
    this.available = true;
    this.submittingEstimate = false;
    this.retrieving = false;
    this.checkInCompleted = false;
    this.summaryCompleted = false;
    this.needsCompleted = false;
    this.negotiationCompleted = false;
    this.pricingCompleted = false;
    this.completed = false;
    this.inspectionCompleted = false;
    this.presentationCompleted = false;
    this.jobSummaryCompleted = false;
    this.documentsCompleted = false;
    this.questions = [
      new CustomerNeedQuestion("How did you hear about Repipe Specialists?", [
        new CustomerNeedPossibleAnswer("Radio", "radio"),
        new CustomerNeedPossibleAnswer("TV", "tv"),
        new CustomerNeedPossibleAnswer("Internet", "internet"),
        new CustomerNeedPossibleAnswer("Direct Mail", "directmail"),
        new CustomerNeedPossibleAnswer("Referral Shop", "referralshop"),
        new CustomerNeedPossibleAnswer("Facebook / Instagram", "social"),
        new CustomerNeedPossibleAnswer("Not Applicable", "na"),
        new CustomerNeedPossibleAnswer("Other", "other")
      ]),
      new CustomerNeedQuestion("What was the primary reason you called us out today?", [
        new CustomerNeedPossibleAnswer("Old Pipes", "oldpipes"),
        new CustomerNeedPossibleAnswer("Leaks", "leaks"),
        new CustomerNeedPossibleAnswer("Water shut off", "watershutoff"),
        new CustomerNeedPossibleAnswer("Low pressure", "lowpressure"),
        new CustomerNeedPossibleAnswer("Rusty Water", "rustywater"),
        new CustomerNeedPossibleAnswer("Insurance related", "insurance"),
        new CustomerNeedPossibleAnswer("Polybutylene pipes", "polybutylene"),
        new CustomerNeedPossibleAnswer("Not Applicable", "na"),
        new CustomerNeedPossibleAnswer("Other", "other")
      ]),
      new CustomerNeedQuestion("What other problems are you having with your current plumbing system?", [
        new CustomerNeedPossibleAnswer("Hot water takes a long time to come out", "hotwater"),
        new CustomerNeedPossibleAnswer("Old water heater", "oldwaterheater"),
        new CustomerNeedPossibleAnswer("Drains", "drains"),
        new CustomerNeedPossibleAnswer("Not Applicable", "na"),
        new CustomerNeedPossibleAnswer("Other", "other")
      ]),
      new CustomerNeedQuestion("How much do you know about the repipe process?", [
        new CustomerNeedPossibleAnswer("None", "none"),
        new CustomerNeedPossibleAnswer("It's been fully explained", "explained"),
        new CustomerNeedPossibleAnswer("Some knowledge", "some"),
        new CustomerNeedPossibleAnswer("Not Applicable", "na"),
        new CustomerNeedPossibleAnswer("Other", "other")
      ]),
      new CustomerNeedQuestion("What is important to you when hiring a company to repipe your home?", [
        new CustomerNeedPossibleAnswer("Good Reviews", "reviews"),
        new CustomerNeedPossibleAnswer("Good rating with the BBB", "bbb"),
        new CustomerNeedPossibleAnswer("Fair price point", "price"),
        new CustomerNeedPossibleAnswer("Backs their warranty", "warranty"),
        new CustomerNeedPossibleAnswer("Many years in business", "yearsinbusiness"),
        new CustomerNeedPossibleAnswer("An all-inclusive job start to finish -- including patching", "allinclusive"),
        new CustomerNeedPossibleAnswer("Not Applicable", "na"),
        new CustomerNeedPossibleAnswer("Other", "other")
      ]),
      new CustomerNeedQuestion("Have you had any plumbers out to talk about your repipe?", [
        new CustomerNeedPossibleAnswer("Yes", "yes"),
        new CustomerNeedPossibleAnswer("No", "no"),
        new CustomerNeedPossibleAnswer("You're the first one", "first"),
        new CustomerNeedPossibleAnswer("Have had a couple of bids", "couplebids"),
        new CustomerNeedPossibleAnswer("Not Applicable", "na"),
        new CustomerNeedPossibleAnswer("Other", "other")
      ])
    ];
    this.documentsValidForEstimate = false;
    this.paymentsValidForEstimate = false;
  }
  get hasPlumbingIssues() {
    return this.opportunityPlumbingIssues.length > 0;
  }
  get friendlyStatus() {
    switch (this.status) {
      case OpportunityStatus.Canceled:
        return "Canceled";
      case OpportunityStatus.Dispositioned:
        return "Dispositioned";
      case OpportunityStatus.InProgress:
        return "In Progress";
      case OpportunityStatus.Open:
        return "Open";
      case OpportunityStatus.PendingSubmission:
        return "Waiting to Submit";
      case OpportunityStatus.Submitted:
        return "Submitted";
      case OpportunityStatus.PendingSignatures:
        return "Awaiting Customer Signatures";
    }
  }
  // CAUTION! DON'T USE IF YOU DON'T KNOW WHAT YOU'RE DOING
  resetToBase() {
    if (this.property?.entity != null) {
      this.property.entity = void 0;
      this.property.photo = void 0;
      this.property.photo_B64 = void 0;
    }
    this.price = void 0;
    this.customPrice = void 0;
    this.accepted = false;
    this.accepted_Utc = void 0;
    this.selectedShopId = void 0;
    this.preferredInstallDate = void 0;
    this.customerNeeds = {};
    this.paymentSetting = void 0;
    this.payments = [];
    this.lenders = [];
    this.price = void 0;
    this.customPrice = void 0;
    this.documents = [];
  }
  checkSummaryProgression() {
    this.summaryCompleted = true;
  }
  checkCheckInProgression() {
    if (this.property?.photo?.fileName != null || this.property?.photo_B64 != null) {
      this.checkInCompleted = true;
    } else {
      this.checkInCompleted = false;
    }
  }
  checkNeedsProgression() {
    let missingQuestion = false;
    for (var i = 0, len = this.questions.length; i < len; i++) {
      const question = this.questions[i];
      const answeredQuestion = this.customerNeeds[question.question];
      if (answeredQuestion == null || answeredQuestion.length <= 0) {
        missingQuestion = true;
        break;
      }
    }
    this.needsCompleted = !missingQuestion;
  }
  inspectionProgressionFixtureCheck(entities) {
    let foundFixture = false;
    for (var i = 0, len = entities.length; i < len; i++) {
      const entity = entities[i];
      const validFixture = entity.fixtures.find((x) => x.valid);
      if (validFixture != null) {
        foundFixture = true;
        break;
      } else if (!foundFixture) {
        foundFixture = this.inspectionProgressionFixtureCheck(entity.entities);
      }
    }
    return foundFixture;
  }
  checkInspectionProgression() {
    let hasAtLeastOneFixture = false;
    if (this.property?.entity != null) {
      hasAtLeastOneFixture = this.inspectionProgressionFixtureCheck(this.property.entity.entities);
    }
    this.inspectionCompleted = hasAtLeastOneFixture && (this.property?.entity?.valid ?? false);
  }
  checkPresentationProgression() {
    this.presentationCompleted = this.inspectionCompleted;
  }
  checkJobSummaryProgression() {
    this.jobSummaryCompleted = this.presentationCompleted;
  }
  checkPricingProgression() {
    this.pricingCompleted = this.customPrice?.finalPrice != null;
  }
  checkDocumentsProgression() {
    let missingDocument = this.documents.length <= 0;
    for (var i = 0, len = this.documents.length; i < len; i++) {
      const document = this.documents[i];
      if (!document.executed) {
        missingDocument = true;
        break;
      }
    }
    this.documentsCompleted = !missingDocument;
  }
  checkCompleted() {
    this.completed = this.status == OpportunityStatus.PendingSubmission || this.status == OpportunityStatus.Submitted;
  }
  checkNegotiationCompleted() {
    this.negotiationCompleted = this.customPrice?.finalPrice != null;
  }
  checkProgression() {
    this.checkSummaryProgression();
    this.checkCheckInProgression();
    this.checkNeedsProgression();
    this.checkInspectionProgression();
    this.checkPresentationProgression();
    this.checkJobSummaryProgression();
    this.checkPricingProgression();
    this.checkDocumentsProgression();
    this.checkNegotiationCompleted();
    this.checkCompleted();
  }
  checkDocumentValidity() {
    let hasInvalidDocument = true;
    if (this.documents.length > 0) {
      this.documents.forEach((document) => {
        if (document.checkSigningValidity == null) {
          document = new OpportunityDocument(document.id, document.name, document.customerFacing, document.shopFacing, document.standalone, document.dynamic, document.alwaysShow, document.displayInSigning, document.mapping, document.displayOrder, document.content, document.executed, document.signers, document.rules, document.documents, document.signatures, document.userContent, document.lastUpdated_Utc, document.created_Utc, document.generatedContent, document.originalHash);
        }
        document.checkSigningValidity();
      });
      const invalidDocument = this.documents.find((x) => x.displayInSigning && x.customerFacing && (!x.customerValid || !x.consultantValid));
      hasInvalidDocument = invalidDocument != null;
    }
    this.documentsValidForEstimate = this.documents.length > 0 && !hasInvalidDocument;
  }
  doesDocumentRuleApply(rule, matches, entities = void 0) {
    if (entities != null) {
      for (var i = 0, len = entities.length; i < len; i++) {
        const entity = entities[i];
        const entityMatches = this.checkIfCriteriaMatched(entity, rule);
        matches.push(...entityMatches);
        if (matches.length == rule.criteria.length) {
          return true;
        } else {
          for (var j = 0, jlen = entity.fixtures.length; j < jlen; j++) {
            const fixture = entity.fixtures[j];
            const fixtureMatches = this.checkIfCriteriaMatched(fixture, rule);
            matches.push(...fixtureMatches);
            if (matches.length == rule.criteria.length) {
              return true;
            }
          }
          return this.doesDocumentRuleApply(rule, matches, entity.entities);
        }
      }
    } else {
      if (this.job != null) {
        const jobMatches = this.checkIfCriteriaMatched(this.job, rule);
        matches.push(...jobMatches);
        if (matches.length == rule.criteria.length) {
          return true;
        }
      }
      if (this.property?.entity != null) {
        const propertyMatches = this.checkIfCriteriaMatched(this.property.entity, rule);
        matches.push(...propertyMatches);
        if (matches.length == rule.criteria.length) {
          return true;
        } else {
          for (var j = 0, jlen = this.property.entity.fixtures.length; j < jlen; j++) {
            const fixture = this.property.entity.fixtures[j];
            const fixtureMatches = this.checkIfCriteriaMatched(fixture, rule);
            matches.push(...fixtureMatches);
            if (matches.length == rule.criteria.length) {
              return true;
            }
          }
          return this.doesDocumentRuleApply(rule, matches, this.property.entity.entities);
        }
      }
    }
    return false;
  }
  checkIfDocumentApplies(document) {
    const matchingRules = [];
    for (var i = 0, len = document.rules.length; i < len; i++) {
      const rule = document.rules[i];
      const ruleMatches = this.doesDocumentRuleApply(rule, []);
      if (ruleMatches) {
        matchingRules.push(rule);
      }
    }
    if (matchingRules.length == document.rules.length) {
      return true;
    } else {
      return false;
    }
  }
  applyDocument(document, documents) {
    const existingDoc = documents.find((x) => x.id == document.id);
    if (existingDoc == null) {
      const apptDoc = new OpportunityDocument(document.id, document.name, document.customerFacing, document.shopFacing, document.standalone, document.dynamic, document.alwaysShow, document.displayInSigning, document.mapping, document.displayOrder, document.content);
      documents.push(apptDoc);
    } else {
      existingDoc.content = document.content;
      existingDoc.mapping = document.mapping;
    }
  }
  removeDocument(document, documents) {
    let existingIndex = this.documents.findIndex((x) => x.id == document.id);
    if (existingIndex > -1) {
      documents.splice(existingIndex, 1);
    }
  }
  generateDocuments(override = false) {
    if (override || this.status != OpportunityStatus.Submitted && this.status != OpportunityStatus.PendingSubmission) {
      if (this.property?.entity != null && this.price?.finalPrice != null) {
        const documents = [];
        let addenda = new OpportunityDocument(generateId(), "Scope of Work Acknowledgments", true, true, false, true, true, true, void 0, 2, "<p>{{logo}}</p><p>{{document.name}}</p>{{timestamp}}");
        for (var i = 0, len = this.availableDocuments.length; i < len; i++) {
          const document = this.availableDocuments[i];
          const applies = this.checkIfDocumentApplies(document);
          if (applies) {
            if (document.standalone) {
              this.applyDocument(document, documents);
            } else {
              const existingAddendum = addenda.documents.find((x) => x.id == document.id);
              if (existingAddendum == null) {
                const addendum = new OpportunityDocument(document.id, document.name, document.customerFacing, document.shopFacing, false, document.dynamic, document.alwaysShow, document.displayInSigning, document.mapping, addenda.documents.length, document.content);
                addenda.documents.push(addendum);
              }
            }
          } else {
            this.removeDocument(document, documents);
          }
        }
        if (addenda.documents.length > 0) {
          documents.push(addenda);
        } else {
          const addendaIndex = this.documents.findIndex((x) => x.name == addenda.name);
          if (addendaIndex > -1) {
            documents.splice(addendaIndex, 1);
          }
        }
        return documents;
      }
    }
    return this.documents;
  }
  // #endregion
  // #region Entities
  flattenEntities(entity = void 0, flattened = []) {
    if (entity == null) {
      if (this.job != null) {
        flattened.push(this.job);
      }
      if (this.property?.entity != null) {
        flattened.push(this.property.entity);
        this.flattenEntities(this.property.entity, flattened);
      }
    } else {
      flattened.push(entity);
      entity.entities.forEach((cEntity) => {
        this.flattenEntities(cEntity, flattened);
      });
    }
    return flattened;
  }
  flattenFixtures(flattened) {
    const fixtures = [];
    flattened.forEach((entity) => {
      fixtures.push(...entity.fixtures);
    });
    return fixtures;
  }
  findEntity(entityId) {
    if (this.property?.entity != null) {
      const flattened = this.flattenEntities();
      for (var i = 0, len = flattened.length; i < len; i++) {
        const entity = flattened[i];
        for (var j = 0, jlen = entity.fixtures.length; j < jlen; j++) {
          const fixture = entity.fixtures[j];
          if (fixture.id == entityId) {
            return fixture;
          }
        }
        for (var j = 0, jlen = entity.entities.length; j < jlen; j++) {
          const childEntity = entity.entities[j];
          if (childEntity.id == entityId) {
            return childEntity;
          }
        }
      }
    }
    return void 0;
  }
  findParent(entityId) {
    if (this.property?.entity != null) {
      const flattened = this.flattenEntities();
      for (var i = 0, len = flattened.length; i < len; i++) {
        const entity = flattened[i];
        for (var j = 0, jlen = entity.fixtures.length; j < jlen; j++) {
          const fixture = entity.fixtures[j];
          if (fixture.id == entityId) {
            return entity;
          }
        }
        for (var j = 0, jlen = entity.entities.length; j < jlen; j++) {
          const childEntity = entity.entities[j];
          if (childEntity.id == entityId) {
            return entity;
          }
        }
      }
    }
    return void 0;
  }
  // #endregion
  // #region Fixtures
  getFixtureCountByName(name, entities = [], count = 0, attributesToMatch = []) {
    entities.forEach((entity) => {
      entity.fixtures.forEach((f) => {
        const fixtureName = f.name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
        if (fixtureName == name) {
          if (attributesToMatch.length > 0) {
            let matchCount = 0;
            f.attributes.forEach((attribute) => {
              const attributeName = attribute.name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
              const attributeValue = attribute.values[0]?.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
              attributesToMatch.forEach((attributeToMatch) => {
                const matchingName = attributeToMatch.name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
                const matchingValue = attributeToMatch.value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
                if (attributeName == matchingName && (attributeValue == matchingValue || attributeValue == "true" && matchingValue == "yes" || attributeValue == "false" && matchingValue == "no")) {
                  matchCount = matchCount + 1;
                }
              });
            });
            if (matchCount == attributesToMatch.length) {
              count = count + 1;
            }
          } else {
            count = count + 1;
          }
        }
      });
      count = this.getFixtureCountByName(name, entity.entities, count, attributesToMatch);
    });
    return count;
  }
  getFixtureCount(type, entities = [], count = 0) {
    entities.forEach((entity) => {
      entity.fixtures.forEach((f) => {
        if (f.imageSrc_Base64 == type) {
          count = count + 1;
        }
      });
      count = this.getFixtureCount(type, entity.entities, count);
    });
    return count;
  }
  renumberFixturesByType(type, entities = [], count = 1) {
    entities.forEach((entity) => {
      entity.fixtures.forEach((f) => {
        if (f.imageSrc_Base64 == type) {
          f.number = count;
          count = count + 1;
        }
      });
      count = this.renumberFixturesByType(type, entity.entities, count);
    });
    return count;
  }
  renumberFixtures() {
    this.availableFixtures.forEach((fc) => {
      fc.fixtures.forEach((af) => {
        this.renumberFixturesByType(af.imageSrc_Base64, this.property?.entity?.entities);
      });
    });
  }
  // #endregion
  // #region Floors
  getLevelCount(entities = [], levelCount = 0) {
    entities.forEach((e) => {
      if (e.type == PropertyEntityType.Structure) {
        levelCount = levelCount + e.entities.length;
      } else {
        levelCount = this.getLevelCount(e.entities, levelCount);
      }
    });
    return levelCount;
  }
  // #endregion
  // #region Rooms
  getRoomTypeCount(type, entities = [], roomCount = 0) {
    entities.forEach((e) => {
      if (e.type == PropertyEntityType.Room && e.name != null) {
        const name = e.name.toLowerCase();
        if (name == type) {
          roomCount = roomCount + 1;
        }
      } else {
        roomCount = this.getRoomTypeCount(type, e.entities, roomCount);
      }
    });
    return roomCount;
  }
  renumberRoomsByType(type, entities = [], roomCount = 0) {
    entities.forEach((e) => {
      if (e.type == PropertyEntityType.Room && e.name != null) {
        const name = e.name.toLowerCase();
        if (name == type) {
          roomCount = roomCount + 1;
          e.number = roomCount;
        }
      } else {
        roomCount = this.renumberRoomsByType(type, e.entities, roomCount);
      }
    });
    return roomCount;
  }
  renumberRooms() {
    this.renumberRoomsByType("bathroom", this.property?.entity?.entities);
  }
  // We support passing in an appointment so we can check one on demand without having stored it first
  validateLenders(log = false, opportunity = this) {
    for (var i = 0, len = opportunity.lenders.length; i < len; i++) {
      const lender = this.lenders[i];
      try {
        if (lender.amountFinanced == null || lender.amountFinanced < 0 || lender.applicationId == null) {
          if (log) {
            console.warn("LENDER VALIDATION: A lender on the appointment is missing required information.");
          }
          return false;
        }
      } catch {
      }
    }
    return true;
  }
  // We support passing in an appointment so we can check one on demand without having stored it first
  validatePaymentDetails(log = false, opportunity = this, hasLender = false) {
    const price = opportunity.customPrice ?? opportunity.price;
    if (price == null) {
      if (log) {
        console.warn("PAYMENT DETAIL VALIDATION: No price on Appointment");
      }
      return false;
    }
    if (opportunity.paymentSetting == null) {
      if (log) {
        console.warn("PAYMENT DETAIL VALIDATION: No payment settings on appointment.");
      }
      return false;
    }
    let totalDetailAmount = 0;
    for (var i = 0, len = opportunity.paymentSetting.details.length; i < len; i++) {
      const detail = opportunity.paymentSetting.details[i];
      if (detail.amount < detail.minimumAmount) {
        if (log) {
          console.warn(`PAYMENT DETAIL VALIDATION: Detail amounts must be greater or equal to their minimum amount.`);
        }
        return false;
      }
      if (detail.maximumAmount != null && detail.amount > detail.maximumAmount) {
        if (log) {
          console.warn(`PAYMENT DETAIL VALIDATION: Detail amounts must be less than or equal to their maximum amounts.`);
        }
        return false;
      }
      if (hasLender) {
        if (detail.lenderAmounts.length == 0) {
          return false;
        } else {
          for (var j = 0, jlen = detail.lenderAmounts.length; j < jlen; j++) {
            const lenderAmount = detail.lenderAmounts[j];
            if (lenderAmount.amount == null) {
              if (log) {
                console.warn("PAYMENT DETAIL VALIDATION: Detale lender amounts must be populated.");
              }
              return false;
            }
          }
        }
      }
      totalDetailAmount = totalDetailAmount + detail.amount;
    }
    if (totalDetailAmount != price.finalPrice) {
      if (log) {
        console.warn("PAYMENT DETAIL VALIDATION: Detail amounts must equal the final price of the job.");
      }
      return false;
    }
    return true;
  }
  // We support passing in an appointment so we can check one on demand without having stored it first
  validatePayments(log = false, opportunity = this) {
    const price = opportunity.customPrice ?? opportunity.price;
    if (price == null) {
      if (log) {
        console.warn("PAYMENT VALIDATION: No price on Appointment");
      }
      return false;
    }
    const deposit = opportunity.paymentSetting?.details.find((x) => x.name.toLowerCase().includes("deposit"));
    if (deposit != null) {
      let totalPaid = 0;
      opportunity.payments.forEach((payment) => {
        totalPaid = totalPaid + payment.amount;
      });
      if (deposit.amount > 0 && totalPaid < deposit.amount) {
        if (log) {
          console.warn("PAYMENT VALIDATION: Total paid is less than deposit amount.");
        }
        return false;
      }
    }
    for (var i = 0, len = opportunity.payments.length; i < len; i++) {
      const payment = opportunity.payments[i];
      if (payment.amount < 0) {
        if (log) {
          console.warn("PAYMENT VALIDATION: A payment can't have a negative amount.");
        }
        return false;
      }
    }
    return true;
  }
  checkPaymentsValidity(log = false) {
    const paymentsValid = this.validatePayments(log);
    const lendersValid = this.validateLenders(log);
    const detailsValid = this.validatePaymentDetails(log);
    this.paymentsValidForEstimate = paymentsValid && lendersValid && detailsValid;
  }
  // #endregion
  // #region Payment Details
  setPaymentSetting(setting) {
    const price = this.customPrice ?? this.price;
    if (price?.finalPrice != null) {
      const setDetails = [];
      for (var i = 0, len = setting.details.length; i < len; i++) {
        const detail = setting.details[i];
        let amount = safePrice(price.finalPrice * (detail.defaultPercent / 100));
        if (detail.maximumAmount != null && amount > detail.maximumAmount) {
          amount = safePrice(detail.maximumAmount);
        }
        if (detail.minimumAmount != null && amount < detail.minimumAmount) {
          amount = safePrice(detail.minimumAmount);
        }
        const setDetail = new PaymentDetail(detail, amount);
        setDetails.push(setDetail);
      }
      const setMethods = [];
      for (var i = 0, len = setting.methods.length; i < len; i++) {
        const method = setting.methods[i];
        const setMethod = Object.assign(new PaymentMethod(), method);
        setMethods.push(setMethod);
      }
      const setSetting = Object.assign(new PaymentSetting(), setting);
      setSetting.details = setDetails;
      setSetting.methods = setMethods;
      this.paymentSetting = setSetting;
    }
  }
  generatePaymentSetting() {
    const price = this.customPrice ?? this.price;
    if (price?.finalPrice != null) {
      let matchedSetting;
      paymentLoop: for (var i = 0, len = this.availablePaymentSettings.length; i < len; i++) {
        const setting = this.availablePaymentSettings[i];
        if (setting.skuTrigger != null) {
          for (var j = 0, jlen = price.details.length; j < jlen; j++) {
            const detail = price.details[j];
            const matchingSku = detail.lineItems.find((x) => x.skuId == setting.skuTrigger);
            if (matchingSku != null) {
              this.setPaymentSetting(setting);
              matchedSetting = setting;
              break paymentLoop;
            }
          }
        }
      }
      if (matchedSetting == null) {
        matchedSetting = this.availablePaymentSettings.find((x) => x.skuTrigger == null);
        if (matchedSetting != null) {
          this.setPaymentSetting(matchedSetting);
        }
      }
    }
  }
  // #endregion
  // #region Pricing
  parseReferenceQuantity(entityId, rule, attributes) {
    const regex = /\[([^\.]+)\.([^\]]*)\](-?\d+)/;
    const match = rule.result.match(regex);
    if (match != null) {
      const referenceId = entityId;
      const refAttribute = match[2].replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
      let quantity = 0;
      const attribute = attributes.find((x) => x.name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase() == refAttribute);
      if (attribute != null) {
        const attrQuantity = Number.parseInt(attribute.values[0]);
        if (match[3] != null) {
          const refQuantity = Number.parseInt(match[3]);
          quantity = attrQuantity + refQuantity;
        } else {
          quantity = attrQuantity;
        }
      }
      const parsedQuantity = {
        referenceId,
        attribute: refAttribute,
        quantity
      };
      return parsedQuantity;
    }
    return null;
  }
  getMatchingEntityAttribute(entity, attributeId) {
    for (var i = 0, len = entity.attributes.length; i < len; i++) {
      const attribute = entity.attributes[i];
      if (attribute.id == attributeId) {
        return attribute;
      }
    }
    const parent = this.findParent(entity.id);
    if (parent != null) {
      const attribute = this.getMatchingEntityAttribute(parent, attributeId);
      if (attribute != null) {
        return attribute;
      }
    } else if (this.job != null) {
      for (var i = 0, len = this.job.attributes.length; i < len; i++) {
        const attribute = this.job.attributes[i];
        if (attribute.id == attributeId) {
          return attribute;
        }
      }
    }
    return void 0;
  }
  checkIfCriteriaMatched(entity, rule) {
    var matches = [];
    for (var i = 0, len = rule.criteria.length; i < len; i++) {
      const criteria = rule.criteria[i];
      let matched = false;
      switch (criteria.triggerType) {
        case EntityCriteriaTriggerType.Attribute:
          {
            let matchingAttribute = this.getMatchingEntityAttribute(entity, criteria.trigger);
            if (matchingAttribute != null) {
              for (var j = 0, jlen = matchingAttribute.values.length; j < jlen; j++) {
                const value = matchingAttribute.values[j];
                let localValue = value.toLowerCase();
                if (value == "true") {
                  localValue = "yes";
                } else if (value == "false") {
                  localValue = "no";
                }
                const possibleValues = [];
                criteria.possibleValues.forEach((value2) => {
                  possibleValues.push(value2.toLowerCase());
                });
                switch (criteria.type) {
                  case EntityRuleCriteriaType.Exact:
                    {
                      if (possibleValues.includes(localValue)) {
                        matches.push(`Value: ${localValue} on Attribute: ${matchingAttribute.name}`);
                        matched = true;
                      }
                    }
                    break;
                  case EntityRuleCriteriaType.GreaterThanOrEqual:
                    {
                      var relValue = criteria.possibleValues[0];
                      var attrValue = localValue;
                      if (relValue != null) {
                        var relValueNum = parseInt(relValue);
                        var attrValueNum = parseInt(attrValue);
                        if (attrValueNum >= relValueNum) {
                          matches.push(`Value: ${attrValueNum} is greater than or equal to ${relValueNum} on Attribute: ${matchingAttribute.name}`);
                          matched = true;
                        }
                      }
                    }
                    break;
                  case EntityRuleCriteriaType.GreaterThan:
                    {
                      var relValue = criteria.possibleValues[0];
                      var attrValue = localValue;
                      if (relValue != null) {
                        var relValueNum = parseInt(relValue);
                        var attrValueNum = parseInt(attrValue);
                        if (attrValueNum > relValueNum) {
                          matches.push(`Value: ${attrValueNum} is greater than ${relValueNum} on Attribute: ${matchingAttribute.name}`);
                          matched = true;
                        }
                      }
                    }
                    break;
                  case EntityRuleCriteriaType.LessThanOrEqual:
                    {
                      var relValue = criteria.possibleValues[0];
                      var attrValue = localValue;
                      if (relValue != null) {
                        var relValueNum = parseInt(relValue);
                        var attrValueNum = parseInt(attrValue);
                        if (attrValueNum <= relValueNum) {
                          matches.push(`Value: ${attrValueNum} is less than or equal to ${relValueNum} on Attribute: ${matchingAttribute.name}`);
                          matched = true;
                        }
                      }
                    }
                    break;
                  case EntityRuleCriteriaType.LessThan:
                    {
                      var relValue = criteria.possibleValues[0];
                      var attrValue = localValue;
                      if (relValue != null) {
                        var relValueNum = parseInt(relValue);
                        var attrValueNum = parseInt(attrValue);
                        if (attrValueNum <= relValueNum) {
                          matches.push(`Value: ${attrValueNum} is less than ${relValueNum} on Attribute: ${matchingAttribute.name}`);
                          matched = true;
                        }
                      }
                    }
                    break;
                }
              }
            }
          }
          break;
        case EntityCriteriaTriggerType.ParentRestriction:
          {
            const parent = this.findParent(entity.id);
            if (parent?.entityId == criteria.trigger) {
              matches.push(`Parent Entity is ${parent.type}`);
            }
          }
          break;
      }
      if (criteria.triggerType == "Attribute") {
      }
    }
    return matches;
  }
  calcPricingForFixture(baseFixture, fixture, details, customPrices = []) {
    for (var i = 0, len = baseFixture.rules.length; i < len; i++) {
      const rule = baseFixture.rules[i];
      var matches = [];
      var entityMatches = this.checkIfCriteriaMatched(fixture, rule);
      if (entityMatches.length == rule.criteria.length) {
        matches.push(...entityMatches);
      }
      if (matches.length > 0) {
        const sku = this.availableSkus.find((x) => x.id == rule.skuId);
        if (sku != null) {
          const skuPrice = customPrices.find((x) => x.skuId == sku.id && x.entityId == fixture.id)?.price ?? sku.price;
          const lineItem = new OpportunityLineItem(fixture, sku.id, sku.category, sku.customerFacing, sku.isAttachment, sku.name, "", safePrice(skuPrice), 0);
          switch (rule.resultType) {
            case EntityRuleResultType.Quantity:
              {
                lineItem.quantity = parseInt(rule.result);
              }
              break;
            case EntityRuleResultType.ReferenceQuantity:
              {
                const parsedQuantity = this.parseReferenceQuantity(fixture.id, rule, fixture.attributes);
                if (parsedQuantity != null) {
                  lineItem.quantity = parsedQuantity.quantity;
                }
              }
              break;
          }
          var detail = {
            ruleId: rule.id,
            matches,
            lineItems: [lineItem],
            result: ""
          };
          details.push(detail);
        }
      }
    }
  }
  calcPricingForFixtures(fixtures, details, customPrices = []) {
    for (var i = 0, len = fixtures.length; i < len; i++) {
      const fixture = fixtures[i];
      let baseFixture = void 0;
      for (var j = 0, jlen = this.availableFixtures.length; j < jlen; j++) {
        const fc = this.availableFixtures[j];
        for (var k = 0, klen = fc.fixtures.length; k < klen; k++) {
          const fcFixture = fc.fixtures[k];
          if (fcFixture.entityId == fixture.entityId) {
            baseFixture = fcFixture;
            break;
          }
        }
      }
      if (baseFixture != null) {
        this.calcPricingForFixture(baseFixture, fixture, details, customPrices);
      }
    }
  }
  calcPricingForEntity(baseEntity, entity, details, customPrices = []) {
    for (var i = 0, len = baseEntity.rules.length; i < len; i++) {
      const rule = baseEntity.rules[i];
      var matches = [];
      var entityMatches = this.checkIfCriteriaMatched(entity, rule);
      if (entityMatches.length == rule.criteria.length) {
        matches.push(...entityMatches);
      }
      if (matches.length > 0) {
        const sku = this.availableSkus.find((x) => x.id == rule.skuId);
        if (sku != null) {
          const skuPrice = customPrices.find((x) => x.skuId == sku.id && x.entityId == entity.id)?.price ?? sku.price;
          const lineItem = new OpportunityLineItem(entity, sku.id, sku.category, sku.customerFacing, sku.isAttachment, sku.name, "", safePrice(skuPrice), 0);
          switch (rule.resultType) {
            case EntityRuleResultType.Quantity:
              {
                lineItem.quantity = parseInt(rule.result);
              }
              break;
            case EntityRuleResultType.ReferenceQuantity:
              {
                const parsedQuantity = this.parseReferenceQuantity(entity.id, rule, entity.attributes);
                if (parsedQuantity != null) {
                  lineItem.quantity = parsedQuantity.quantity;
                }
              }
              break;
          }
          var detail = {
            ruleId: rule.id,
            matches,
            lineItems: [lineItem],
            result: ""
          };
          details.push(detail);
        }
      }
    }
    this.calcPricingForFixtures(entity.fixtures, details, customPrices);
    this.calcPricingForEntities(details, entity.entities, customPrices);
  }
  calcPricingForEntities(details, entities = void 0, customPrices = []) {
    if (entities != null) {
      for (var i = 0, len = entities.length; i < len; i++) {
        const entity = entities[i];
        let baseEntity = void 0;
        this.availableEntities.forEach((ec) => {
          for (var j2 = 0, jlen2 = ec.entities.length; j2 < jlen2; j2++) {
            const ecEntity = ec.entities[j2];
            if (ecEntity.type == entity.type) {
              baseEntity = ecEntity;
              break;
            }
          }
        });
        if (baseEntity != null) {
          this.calcPricingForEntity(baseEntity, entity, details, customPrices);
        }
      }
    } else {
      if (this.job != null) {
        let baseEntity = void 0;
        for (var i = 0, len = this.availableEntities.length; i < len; i++) {
          const ec = this.availableEntities[i];
          for (var j = 0, jlen = ec.entities.length; j < jlen; j++) {
            const ecEntity = ec.entities[j];
            if (ecEntity.type == this.job.type) {
              baseEntity = ecEntity;
              break;
            }
          }
        }
        if (baseEntity != null) {
          this.calcPricingForEntity(baseEntity, this.job, details, customPrices);
        }
      }
      if (this.property?.entity != null) {
        let baseEntity = void 0;
        for (var i = 0, len = this.availableEntities.length; i < len; i++) {
          const ec = this.availableEntities[i];
          for (var j = 0, jlen = ec.entities.length; j < jlen; j++) {
            const ecEntity = ec.entities[j];
            if (ecEntity.type == this.property.entity.type) {
              baseEntity = ecEntity;
              break;
            }
          }
        }
        if (baseEntity != null) {
          this.calcPricingForEntity(baseEntity, this.property.entity, details, customPrices);
        }
      }
    }
  }
  calcAblPrice(parPrice) {
    let ablPrice = parPrice * 0.8;
    if (ablPrice <= 0) {
      ablPrice = 0;
    }
    return safePrice(ablPrice);
  }
  calcTargetPrice(parPrice) {
    return safePrice(parPrice + parPrice * 0.4);
  }
  calcParPrice(customItems, details) {
    let parPrice = 0;
    customItems.forEach((item) => {
      if (item.price != null) {
        parPrice = parPrice + item.price;
      }
    });
    details.forEach((detail) => {
      detail.lineItems.forEach((lineItem) => {
        parPrice = parPrice + lineItem.price * lineItem.quantity;
      });
    });
    if (parPrice <= 0) {
      parPrice = 0;
    }
    return safePrice(parPrice);
  }
  calcPromotionalPrice(retailPrice) {
    let promotionalPrice = retailPrice - retailPrice * 0.2;
    return safePrice(promotionalPrice);
  }
  calcFinalPrice(targetPromoPrice) {
    return safePrice(targetPromoPrice);
  }
  calcPricing(override = false, markChanged = true, store = true) {
    if (this.status == OpportunityStatus.InProgress || override) {
      const originalFinalPrice = this.price?.finalPrice;
      const details = [];
      const considerations = [];
      this.price?.customItems.forEach((item) => {
        considerations.push(item);
      });
      const customPrices = [];
      if (this.price != null) {
        for (var i = 0, len = this.price.details.length; i < len; i++) {
          const detail = this.price.details[i];
          for (var j = 0, jlen = detail.lineItems.length; j < jlen; j++) {
            const lineItem = detail.lineItems[j];
            if (lineItem.skuCategory == "CUSTOM") {
              customPrices.push({
                entityId: lineItem.entityId ?? "",
                skuId: lineItem.skuId,
                price: lineItem.price
              });
            }
          }
        }
      }
      this.calcPricingForEntities(details, void 0, customPrices);
      const parPrice = this.calcParPrice(considerations, details);
      const ablPrice = this.calcAblPrice(parPrice);
      const targetPrice = this.calcTargetPrice(parPrice);
      const targetPromoPrice = this.calcPromotionalPrice(targetPrice);
      let finalPrice = this.price?.finalPrice;
      if (targetPromoPrice != this.price?.targetPromoPrice) {
        finalPrice = this.calcFinalPrice(targetPromoPrice);
      }
      if (finalPrice != null) {
        finalPrice = safePrice(finalPrice);
      }
      const newPrice = new OpportunityPrice(void 0, details, this.price?.customItems, this.price?.appliedPromotions, finalPrice, targetPrice, ablPrice, parPrice, targetPromoPrice);
      if (store) {
        this.price = newPrice;
      }
      if (newPrice.finalPrice != originalFinalPrice && markChanged) {
        this.priceChanged$.next();
      }
      let total = 0;
      newPrice.details.forEach((detail) => {
        detail.lineItems.forEach((lineItem) => {
          total = total + lineItem.price;
        });
      });
      return newPrice;
    }
    return this.price;
  }
  // #endregion
  // #region Attributes
  updateMappedDefaultValues(attribute, parentEntity) {
    const regex = /^\[entity\.type=([^\]]+)\](?:\.\[attribute\.[^\]]+\])*\.\{\{([0-9]+|count)\}\}$/;
    const match = attribute.defaultValue?.match(regex);
    if (match) {
      const entityType = match[1];
      const countStr = match[2];
      const attributes = {};
      const iterator = attribute.defaultValue.matchAll(/\[attribute\.([^=]+)=([^[\]]+)\]/g);
      for (const m of iterator) {
        attributes[m[1]] = m[2];
      }
      const result = {
        entityType,
        attributes,
        count: countStr === "count" ? "count" : Number(countStr)
      };
      switch (result.entityType) {
        case "room":
          {
            for (const [key, value] of Object.entries(result.attributes)) {
              switch (key) {
                case "roomtype":
                  {
                    const roomCount = this.getRoomTypeCount(value, parentEntity?.entities);
                    attribute.values = [`${roomCount}`];
                  }
                  break;
              }
            }
          }
          break;
        case "hosebibb":
          {
            const attributesToMatch = [];
            for (const [key, value] of Object.entries(result.attributes)) {
              attributesToMatch.push({
                name: key,
                value
              });
            }
            const fixtureCount = this.getFixtureCountByName(entityType, parentEntity?.entities, 0, attributesToMatch);
            attribute.values = [`${fixtureCount}`];
          }
          break;
      }
    }
  }
  updateAttributes(entities = void 0) {
    if (entities != null) {
      entities.forEach((e) => {
        e.attributes.forEach((a) => {
          this.updateMappedDefaultValues(a, e);
        });
        this.updateAttributes(e.entities);
      });
    } else if (this?.property?.entity != null) {
      this.property.entity.attributes.forEach((attribute) => {
        if (this.property?.entity != null) {
          this.updateMappedDefaultValues(attribute, this.property.entity);
        }
      });
      this.property.entity.entities.forEach((e) => {
        e.attributes.forEach((a) => {
          this.updateMappedDefaultValues(a, e);
        });
        this.updateAttributes(e.entities);
      });
    }
  }
  findAttributes(entity, id, entities = void 0, attributes = []) {
    if (entities != null) {
      for (var i = 0, len = entities.length; i < len; i++) {
        const entity2 = entities[i];
        for (var j = 0, jlen = entity2.attributes.length; j < jlen; j++) {
          const attribute = entity2.attributes[j];
          if (attribute.id == id) {
            attributes.push(attribute);
          }
        }
        for (var j = 0, jlen = entity2.fixtures.length; j < jlen; j++) {
          const fixture = entity2.fixtures[j];
          for (var k = 0, klen = fixture.attributes.length; k < klen; k++) {
            const attribute = fixture.attributes[k];
            if (attribute.id == id) {
              attributes.push(attribute);
            }
          }
        }
        this.findAttributes(entity2, id, entity2.entities, attributes);
      }
    } else {
      for (var i = 0, len = entity.attributes.length; i < len; i++) {
        const attribute = entity.attributes[i];
        if (attribute.id == id) {
          attributes.push(attribute);
        }
      }
      for (var i = 0, len = entity.fixtures.length; i < len; i++) {
        const fixture = entity.fixtures[i];
        for (var j = 0, jlen = fixture.attributes.length; j < jlen; j++) {
          const attribute = fixture.attributes[j];
          if (attribute.id == id) {
            attributes.push(attribute);
          }
        }
      }
      this.findAttributes(entity, id, entity.entities, attributes);
    }
    return attributes;
  }
  getAvailableEntities() {
    const entities = [];
    this.availableEntities.forEach((category) => {
      category.entities.forEach((e) => {
        entities.push(e);
      });
    });
    return entities;
  }
  getAvailableFixtures() {
    const fixtures = [];
    this.availableFixtures.forEach((category) => {
      category.fixtures.forEach((f) => {
        fixtures.push(f);
      });
    });
    return fixtures;
  }
  getBaseRules(entity, attributeId) {
    const availableEntities = this.getAvailableEntities();
    for (var i = 0, len = availableEntities.length; i < len; i++) {
      const availEntity = availableEntities[i];
      const attribute = availEntity.attributes.find((x) => x.id == attributeId);
      if (attribute != null) {
        return attribute.rules;
      }
    }
    const availableFixtures = this.getAvailableFixtures();
    for (var i = 0, len = availableFixtures.length; i < len; i++) {
      const availFixture = availableFixtures[i];
      if (availFixture.entityId == entity.entityId) {
        const attribute = availFixture.attributes.find((x) => x.id == attributeId);
        if (attribute != null) {
          return attribute.rules;
        }
      }
    }
    return [];
  }
  processAttributeRules(entities = void 0) {
    if (entities != null) {
      entities.forEach((entity) => {
        if (entity.attributes.length > 0) {
          const targetAttributes = [];
          entity.attributes.forEach((attribute) => {
            this.processRulesForAttribute(attribute);
          });
        }
        entity.fixtures.forEach((fixture) => {
          const targetAttributes = [];
          fixture.attributes.forEach((attribute) => {
            this.processRulesForAttribute(attribute);
          });
        });
        this.processAttributeRules(entity.entities);
      });
    } else if (this?.property?.entity != null) {
      this.property.entity.attributes.forEach((attribute) => {
        this.processRulesForAttribute(attribute);
      });
      this.processAttributeRules(this.property.entity.entities);
    }
  }
  processRulesForAttribute(attribute) {
    if (attribute.appliedRules != null) {
      let shouldDisable = false;
      let shouldHide = false;
      let newValue;
      attribute.appliedRules.forEach((rule) => {
        if (rule.shouldDisable) {
          shouldDisable = true;
          shouldHide = true;
        }
        if (rule.targetValue != null) {
          newValue = rule.targetValue;
        }
      });
      if (shouldDisable) {
        attribute.disabled = true;
        attribute.values = [];
      } else {
        attribute.disabled = false;
      }
      if (shouldHide) {
        attribute.hiddenByRule = true;
      } else {
        attribute.hiddenByRule = false;
      }
      if (newValue != null) {
        attribute.values = [newValue];
      }
    }
  }
  applyAttributeRules(entities = void 0) {
    if (entities != null) {
      entities.forEach((entity) => {
        if (entity.attributes.length > 0) {
          const targetAttributes = [];
          entity.attributes.forEach((attribute) => {
            this.applyRulesForAttribute(entity, attribute, targetAttributes);
          });
        }
        entity.fixtures.forEach((fixture) => {
          const targetAttributes = [];
          fixture.attributes.forEach((attribute) => {
            this.applyRulesForAttribute(fixture, attribute, targetAttributes);
          });
        });
        this.applyAttributeRules(entity.entities);
      });
    } else if (this?.property?.entity != null) {
      const targetAttributes = [];
      this.property.entity.attributes.forEach((attribute) => {
        this.applyRulesForAttribute(this.property.entity, attribute, targetAttributes);
      });
      if (this.job != null) {
        this.job.attributes.forEach((attribute) => {
          this.applyRulesForAttribute(this.job, attribute, targetAttributes);
        });
      }
      this.applyAttributeRules(this.property.entity.entities);
      this.processAttributeRules();
    }
  }
  applyRulesForAttribute(entity, attribute, targetAttributes) {
    const rules = this.getBaseRules(entity, attribute.id);
    for (var i = 0, len = rules.length; i < len; i++) {
      const rule = rules[i];
      let targetAttributes2 = [];
      if (entity instanceof OpportunityPropertyEntity) {
        targetAttributes2 = this.findAttributes(entity, rule.targetAttributeId);
      } else {
        entity.attributes.forEach((attribute2) => {
          if (attribute2.id == rule.targetAttributeId) {
            targetAttributes2.push(attribute2);
          }
        });
      }
      let triggerValue = rule.triggerValue.toLocaleLowerCase();
      if (attribute.type == EntityAttributeType.Boolean) {
        if (triggerValue.toLocaleLowerCase() == "yes") {
          triggerValue = "true";
        } else {
          triggerValue = "false";
        }
      }
      targetAttributes2.forEach((targetAttribute) => {
        const sanitizedValues = [];
        attribute.values.forEach((value) => {
          sanitizedValues.push(value.toLocaleLowerCase());
        });
        if (targetAttribute.appliedRules == null) {
          targetAttribute.appliedRules = [];
        }
        if (sanitizedValues.includes(triggerValue)) {
          const ruleIndex = targetAttribute.appliedRules.findIndex((x) => x.id == rule.id);
          if (ruleIndex < 0) {
            targetAttribute.appliedRules.push(rule);
          }
        } else {
          const ruleIndex = targetAttribute.appliedRules.findIndex((x) => x.id == rule.id);
          if (ruleIndex > -1) {
            targetAttribute.appliedRules.splice(ruleIndex, 1);
          }
        }
      });
    }
  }
  // #endregion
  toJSON() {
    const safe = deepClone(this);
    return safe;
  }
};
var OpportunityStatus;
(function(OpportunityStatus2) {
  OpportunityStatus2["Open"] = "Open";
  OpportunityStatus2["Canceled"] = "Canceled";
  OpportunityStatus2["InProgress"] = "InProgress";
  OpportunityStatus2["PendingSignatures"] = "PendingSignatures";
  OpportunityStatus2["PendingSubmission"] = "PendingSubmission";
  OpportunityStatus2["Submitted"] = "Submitted";
  OpportunityStatus2["Dispositioned"] = "Dispositioned";
})(OpportunityStatus || (OpportunityStatus = {}));
var OpportunityProperty = class extends Property {
  toFlat() {
    return {
      yearBuilt: this.yearBuilt,
      existingPipeTypes: this.existingPipeTypes,
      foundationTypes: this.foundationTypes,
      type: this.type,
      listedBathrooms: this.listedBathrooms,
      listedStories: this.listedStories,
      listedSquareDimensions: this.listedSquareDimensions,
      dimensionsMeasurementType: this.dimensionsMeasurementType,
      photo: this.photo,
      photo_B64: this.photo_B64
    };
  }
};
var OpportunityPropertyEntity = class extends PropertyEntity {
  get hasInvalidAttributes() {
    const invalidAttribute = this.attributes.find((x) => !x.valid);
    return invalidAttribute != null;
  }
  get hasInvalidEntities() {
    const invalidEntity = this.entities.find((x) => !x.valid);
    return invalidEntity != null;
  }
  get hasInvalidFixtures() {
    const invalidFixture = this.fixtures.find((x) => !x.valid);
    return invalidFixture != null;
  }
  get valid() {
    const photosCount = this.photos.length + this.photos_B64.length;
    if (this.hasInvalidAttributes || this.hasInvalidFixtures || this.hasInvalidEntities || this.type == PropertyEntityType.Room && photosCount == 0) {
      return false;
    }
    return true;
  }
  constructor(id, entityId, type, name, number, attributes = [], fixtures = [], renderDimensions, metafields, rules, photos = [], photos_B64 = [], render, localRender, notes) {
    super(id, entityId, type, name, attributes, fixtures);
    this.entities = [];
    this.fixtures = [];
    this.metafields = {};
    this.photos = [];
    this.photos_B64 = [];
    this.number = 1;
    this.resizing = false;
    this.moving = false;
    this.active = false;
    this.fixtureAdded$ = new Subject();
    this.renderDimensions = new PropertyEntityRenderDimensions(renderDimensions.x, renderDimensions.y, renderDimensions.width, renderDimensions.height, renderDimensions.rotation);
    this.metafields = metafields;
    this.rules = rules;
    this.photos = photos;
    this.photos_B64 = photos_B64;
    this.render = render;
    this.localRender = localRender;
    this.notes = notes;
    this.number = number;
    this.fixtureAdded$ = new Subject();
  }
  toFlat() {
    return {
      id: this.id,
      entityId: this.entityId,
      type: this.type,
      name: this.name,
      metafields: this.metafields,
      number: this.number
    };
  }
  toJSON() {
    return {
      id: this.id,
      entityId: this.entityId,
      type: this.type,
      name: this.name,
      attributes: this.attributes,
      entities: this.entities,
      fixtures: this.fixtures,
      rules: this.rules,
      notes: this.notes,
      renderDimensions: this.renderDimensions,
      metafields: this.metafields,
      photos: this.photos,
      photos_B64: this.photos_B64,
      render: this.render,
      number: this.number
    };
  }
};
var OpportunityFixture = class extends Fixture {
  get hasInvalidAttributes() {
    const invalidAttribute = this.attributes.find((x) => !x.valid);
    return invalidAttribute != null;
  }
  get valid() {
    const photosCount = this.photos.length + this.photos_B64.length;
    if (this.hasInvalidAttributes || photosCount == 0) {
      return false;
    }
    return true;
  }
  constructor(id, entityId, name, imageSrc_Base64, number, attributes = [], renderDimensions, photos = [], photos_B64 = [], notes) {
    super(id, entityId, name, imageSrc_Base64, attributes);
    this.photos = [];
    this.photos_B64 = [];
    this.number = 1;
    this.rotating = false;
    this.resizing = false;
    this.moving = false;
    this.added = false;
    this.renderDimensions = new PropertyEntityRenderDimensions(renderDimensions.x, renderDimensions.y, renderDimensions.width, renderDimensions.height, renderDimensions.rotation);
    this.photos_B64 = photos_B64;
    this.photos = photos;
    this.notes = notes;
    this.number = number;
  }
  toJSON() {
    return {
      id: this.id,
      entityId: this.entityId,
      name: this.name,
      imageSrc_Base64: this.imageSrc_Base64,
      attributes: this.attributes,
      rules: this.rules,
      notes: this.notes,
      renderDimensions: this.renderDimensions,
      photos: this.photos,
      photos_B64: this.photos_B64,
      number: this.number
    };
  }
};
var NoteAudience;
(function(NoteAudience2) {
  NoteAudience2["Customer"] = "Customer";
  NoteAudience2["Shop"] = "Shop";
})(NoteAudience || (NoteAudience = {}));
var ExportedOpportunity = class {
  constructor(opportunity, entries) {
    this.ledger = [];
    this.opportunity = opportunity;
    this.ledger = entries;
  }
};

// src/app/shared/data/services/storage.service.ts
var _StorageService = class _StorageService {
  constructor(http) {
    this.http = http;
    this.dbName = "repipeDb";
    this.db = null;
    this.remoteStoringLedgerEntries = false;
    this.initializeDb();
  }
  // #region Users
  initUserStore(db, transaction) {
    const existingStore = db.objectStoreNames.contains("users");
    let userStore;
    if (existingStore) {
      userStore = transaction.objectStore("users");
    } else {
      userStore = db.createObjectStore("users", {
        keyPath: "id"
      });
    }
    this.createIndex(userStore, "byFirstName", "firstName");
    this.createIndex(userStore, "byLastName", "lastName");
    this.createIndex(userStore, "byPhone", "phone");
    this.createIndex(userStore, "byEmail", "emailAddress");
    this.createIndex(userStore, "byManagerId", "manager.id");
    this.createIndex(userStore, "byManagerFirstName", "manager.firstName");
    this.createIndex(userStore, "byManagerLastName", "manager.lastName");
  }
  getUser(id) {
    return __async(this, null, function* () {
      return this.getItem("users", `${id}`);
    });
  }
  deleteUser(id) {
    return __async(this, null, function* () {
      yield this.deleteItem("users", `${id}`);
    });
  }
  storeUser(user) {
    return __async(this, null, function* () {
      yield this.storeItem("users", user);
    });
  }
  // #endregion
  // #region Content
  initImageStore(db, transaction) {
    const existingStore = db.objectStoreNames.contains("images");
    let userStore;
    if (existingStore) {
      userStore = transaction.objectStore("images");
    } else {
      userStore = db.createObjectStore("images", {
        keyPath: "src"
      });
    }
  }
  getImage(src) {
    return __async(this, null, function* () {
      return this.getItem("images", `${src}`);
    });
  }
  deleteImage(src) {
    return __async(this, null, function* () {
      yield this.deleteItem("images", `${src}`);
    });
  }
  storeImage(image) {
    return __async(this, null, function* () {
      yield this.storeItem("images", image, false);
    });
  }
  initOpportunityLedgerStore(db, transaction) {
    const existingStore = db.objectStoreNames.contains("opportunity_ledger");
    let ledgerStore;
    if (existingStore) {
      ledgerStore = transaction.objectStore("opportunity_ledger");
    } else {
      ledgerStore = db.createObjectStore("opportunity_ledger", {
        keyPath: "id"
      });
    }
    this.createIndex(ledgerStore, "byOpportunityId", "opportunityId");
    this.createIndex(ledgerStore, "byUserId", "userId");
    this.createIndex(ledgerStore, "byType", "type");
    this.createIndex(ledgerStore, "bySubType", "subType");
    this.createIndex(ledgerStore, "byTimeStamp", "timeStamp_Utc");
    this.createIndex(ledgerStore, "byRemoteStored", "remoteStoredStatus");
  }
  getOpportunityLedgerEntriesByOpportunityId(opportunityId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let entryIds = yield this.getOpportunityLedgerEntryIdsForOpportunity(opportunityId);
      this.getOpportunityLedgerEntries(9999, entryIds).then((entries) => {
        res(entries);
      }).catch(() => {
        rej();
      });
    }));
  }
  getOpportunityLedgerEntriesByType(opportunityId, type) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunity_ledger"], "readonly");
      const store = transaction.objectStore("opportunity_ledger");
      const index = store.index("byType");
      const keyRange = IDBKeyRange.only(type);
      const request = index.openCursor(keyRange);
      const matchingEntries = [];
      request.onsuccess = () => {
        const cursor = request.result;
        if (cursor) {
          const entry = cursor.value;
          if (opportunityId !== void 0) {
            if (entry.opportunityid === opportunityId) {
              matchingEntries.push(entry);
            }
          } else {
            matchingEntries.push(entry);
          }
          cursor.continue();
        } else {
          res(matchingEntries);
        }
      };
      request.onerror = (event) => {
        rej(request.error);
      };
    }));
  }
  getOpportunityLedgerEntries(limit = 9999, entryIds = void 0, latestIds = void 0, remoteStoredIds = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunity_ledger"], "readonly");
      const store = transaction.objectStore("opportunity_ledger");
      const scheduledIndex = store.index("byTimeStamp");
      const scheduledRequest = scheduledIndex.openCursor(void 0, "prev");
      const results = [];
      scheduledRequest.onsuccess = function(event) {
        const cursor = this.result;
        if (cursor != null && results.length < limit) {
          const entry = cursor.value;
          if ((entryIds == null || entryIds.includes(entry.id)) && (latestIds == null || latestIds.includes(entry.id)) && (remoteStoredIds == null || remoteStoredIds.includes(entry.id))) {
            results.push(entry);
          }
          cursor.continue();
        } else {
          res(results);
        }
      };
      scheduledRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  getOpportunityLedgerEntryIdsForOpportunity(opportunityId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunity_ledger"], "readonly");
      const store = transaction.objectStore("opportunity_ledger");
      const userIndex = store.index("byOpportunityId");
      const userRange = IDBKeyRange.only(opportunityId);
      const userRequest = userIndex.getAllKeys(userRange);
      const matchingIds = [];
      userRequest.onsuccess = function(event) {
        matchingIds.push(...this.result);
        res(matchingIds);
      };
      userRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  getLatestOpportunityLedgerEntryIds(lastProcessed_Utc) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunity_ledger"], "readonly");
      const store = transaction.objectStore("opportunity_ledger");
      const index = store.index("byTimeStamp");
      const lowerBound = lastProcessed_Utc;
      const range = IDBKeyRange.lowerBound(lowerBound);
      const request = index.getAllKeys(range);
      const matchingIds = [];
      request.onsuccess = function(event) {
        matchingIds.push(...this.result);
        res(matchingIds);
      };
      request.onerror = (event) => {
        rej();
      };
    }));
  }
  getLatestOpportunityLedgerEntries(lastProcesssed_Utc, opportunityId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let oppEntryIds = yield this.getOpportunityLedgerEntryIdsForOpportunity(opportunityId);
      const latestEntryIds = yield this.getLatestOpportunityLedgerEntryIds(lastProcesssed_Utc);
      this.getOpportunityLedgerEntries(9999, oppEntryIds, latestEntryIds).then((entries) => {
        res(entries);
      }).catch(() => {
        rej();
      });
    }));
  }
  deleteLedgerEntriesForOpportunity(opportunityId) {
    return __async(this, null, function* () {
      const entries = yield this.getOpportunityLedgerEntriesByOpportunityId(opportunityId);
      for (var i = 0, len = entries.length; i < len; i++) {
        const entry = entries[i];
        yield this.deleteItem("opportunity_ledger", entry.id);
      }
    });
  }
  storeOpportunityLedgerEntry(entry, remoteStore = true) {
    return new Promise((res, rej) => __async(this, null, function* () {
      entry.timeStamp_Utc = new Date(entry.timeStamp_Utc);
      this.storeItem("opportunity_ledger", entry).then(() => {
        if (remoteStore) {
          this.remoteStoreOpportunityLedgerEntries(entry.opportunityId).subscribe();
        }
        res();
      }).catch(() => {
        rej();
      });
    }));
  }
  // Default to 0 for those that have NOT been stored remotely
  getNotRemoteStoredLedgerEntries() {
    return new Promise((res, rej) => __async(this, null, function* () {
      let remoteStoredIds = yield this.getRemoteStoredLedgerEntryIds(0);
      this.getOpportunityLedgerEntries(9999, void 0, void 0, remoteStoredIds).then((entries) => {
        res(entries);
      }).catch(() => {
        rej();
      });
    }));
  }
  getNotRemoteStoredLedgerEntriesForOpportunity(opportunityId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let entryIds = yield this.getOpportunityLedgerEntryIdsForOpportunity(opportunityId);
      let remoteStoredIds = yield this.getRemoteStoredLedgerEntryIds(0);
      this.getOpportunityLedgerEntries(9999, entryIds, void 0, remoteStoredIds).then((entries) => {
        res(entries);
      }).catch(() => {
        rej();
      });
    }));
  }
  getRemoteStoredLedgerEntryIds(status = 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunity_ledger"], "readonly");
      const store = transaction.objectStore("opportunity_ledger");
      const index = store.index("byRemoteStored");
      const keyRange = IDBKeyRange.only(status);
      const request = index.getAllKeys(keyRange);
      const matchingIds = [];
      request.onsuccess = function(event) {
        matchingIds.push(...this.result);
        res(matchingIds);
      };
      request.onerror = (event) => {
        console.error("Error fetching not-remote-stored ledger entry IDs:", event);
        rej(event);
      };
    }));
  }
  remoteStoreOpportunityLedgerEntries$(entriesToSync) {
    const url = `{{api}}/opportunities/ledger/store`;
    const headers = { "Content-Type": "application/json" };
    const request = {
      entries: entriesToSync
    };
    return this.http.post(url, JSON.stringify(request), { headers }).pipe(tap((response) => __async(this, null, function* () {
      if (response.data != null) {
        for (var i = 0, len = response.data.length; i < len; i++) {
          const responseEntry = response.data[i];
          const entry = entriesToSync.find((x) => x.id == responseEntry.id);
          if (entry != null) {
            entry.body = responseEntry.body;
          }
        }
      }
      for (var i = 0, len = entriesToSync.length; i < len; i++) {
        const entry = entriesToSync[i];
        entry.remoteStoredStatus = 1;
        entry.remoteStored_Utc = /* @__PURE__ */ new Date();
        yield this.storeOpportunityLedgerEntry(entry);
      }
    })));
  }
  remoteStoreOpportunityLedgerEntries(opportunityId = void 0) {
    return new Observable((o) => {
      if (!this.remoteStoringLedgerEntries) {
        window.clearTimeout(this.ledgerStoreDebounce);
        this.ledgerStoreDebounce = window.setTimeout(() => __async(this, null, function* () {
          this.remoteStoringLedgerEntries = true;
          let entriesToSync = [];
          if (opportunityId != null) {
            entriesToSync = yield this.getNotRemoteStoredLedgerEntriesForOpportunity(opportunityId);
          } else {
            entriesToSync = yield this.getNotRemoteStoredLedgerEntries();
          }
          if (entriesToSync.length > 0) {
            const batchSize = 1;
            const batches = [];
            for (let i = 0; i < entriesToSync.length; i += batchSize) {
              batches.push(entriesToSync.slice(i, i + batchSize));
            }
            from(batches).pipe(mergeMap((batch) => this.remoteStoreOpportunityLedgerEntries$(batch), 1), toArray(), tap(() => {
              this.remoteStoringLedgerEntries = false;
              o.next();
              o.complete();
            }), catchError((e) => {
              this.remoteStoringLedgerEntries = false;
              o.error(e);
              o.complete();
              return EMPTY;
            })).subscribe();
          } else {
            this.remoteStoringLedgerEntries = false;
            o.next();
            o.complete();
          }
        }), 1e3);
      }
    });
  }
  // #endregion
  // #region Opportunities
  initOpportunityStore(db, transaction) {
    const existingStore = db.objectStoreNames.contains("opportunities");
    let opportunityStore;
    if (existingStore) {
      opportunityStore = transaction.objectStore("opportunities");
    } else {
      opportunityStore = db.createObjectStore("opportunities", {
        keyPath: "id"
      });
    }
    this.createIndex(opportunityStore, "byJobId", "jobId");
    this.createIndex(opportunityStore, "byAssignedId", "assignedTo.id");
    this.createIndex(opportunityStore, "byAssignedFirstName", "assignedTo.firstName");
    this.createIndex(opportunityStore, "byAssignedLastName", "assignedTo.lastName");
    this.createIndex(opportunityStore, "byCustomerFirstName", "customer.firstName");
    this.createIndex(opportunityStore, "byCustomerLastName", "customer.lastName");
    this.createIndex(opportunityStore, "byScheduled", "scheduled_Utc");
    this.createIndex(opportunityStore, "byLastUpdated", "lastUpdated_Utc");
    this.createIndex(opportunityStore, "byStatus", "status");
  }
  getOpportunity(id) {
    return this.getItem("opportunities", id);
  }
  getOpportunities(limit = 20, userOpportunityIds = void 0, statusOpportunityIds = void 0, dateOpportunityIds = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunities"], "readonly");
      const store = transaction.objectStore("opportunities");
      const scheduledIndex = store.index("byScheduled");
      const scheduledRequest = scheduledIndex.openCursor(void 0, "prev");
      const results = [];
      scheduledRequest.onsuccess = function(event) {
        const cursor = this.result;
        if (cursor != null && results.length < limit) {
          const appointment = cursor.value;
          if ((statusOpportunityIds == null || statusOpportunityIds.includes(appointment.id)) && (userOpportunityIds == null || userOpportunityIds.includes(appointment.id)) && (dateOpportunityIds == null || dateOpportunityIds.includes(appointment.id))) {
            results.push(appointment);
          }
          cursor.continue();
        } else {
          res(results);
        }
      };
      scheduledRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  getOpportunitiesForUserId(userId, count = 20) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let userOpportunityIds = yield this.getOpportunityIdsForUser(userId);
      this.getOpportunities(count, userOpportunityIds, void 0, void 0).then((appointments) => {
        res(appointments);
      }).catch(() => {
        rej();
      });
    }));
  }
  getOpportunityIdsForUser(userId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunities"], "readonly");
      const store = transaction.objectStore("opportunities");
      const userIndex = store.index("byAssignedId");
      const userRange = IDBKeyRange.only(userId);
      const userRequest = userIndex.getAllKeys(userRange);
      const matchingUserIds = [];
      userRequest.onsuccess = function(event) {
        matchingUserIds.push(...this.result);
        res(matchingUserIds);
      };
      userRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  getOpportunityIdsForStatus(status) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunities"], "readonly");
      const store = transaction.objectStore("opportunities");
      const statusIndex = store.index("byStatus");
      const range = IDBKeyRange.only(status);
      const request = statusIndex.getAllKeys(range);
      const matchingUserIds = [];
      request.onsuccess = function(event) {
        matchingUserIds.push(...this.result);
        res(matchingUserIds);
      };
      request.onerror = (event) => {
        rej();
      };
    }));
  }
  getOpportunitiesForStatus(status, userId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      const userAppointmentIds = yield this.getOpportunityIdsForUser(userId);
      const statusAppointmentIds = yield this.getOpportunityIdsForStatus(status);
      this.getOpportunities(20, userAppointmentIds, statusAppointmentIds).then((appointments) => {
        res(appointments);
      }).catch(() => {
        rej();
      });
    }));
  }
  getOpportunityIdsForDate(date) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunities"], "readonly");
      const store = transaction.objectStore("opportunities");
      const index = store.index("byScheduled");
      const lowerBound = /* @__PURE__ */ new Date(`${date}T00:00:00`);
      const upperBound = /* @__PURE__ */ new Date(`${date}T23:59:59`);
      const range = IDBKeyRange.bound(lowerBound, upperBound);
      const request = index.getAllKeys(range);
      const matchingIds = [];
      request.onsuccess = function(event) {
        matchingIds.push(...this.result);
        res(matchingIds);
      };
      request.onerror = (event) => {
        rej();
      };
    }));
  }
  getOpportunitiesForDate(date, userId = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let userOpportunityIds;
      if (userId != null) {
        userOpportunityIds = yield this.getOpportunityIdsForUser(userId);
      }
      const dateAppointmentIds = yield this.getOpportunityIdsForDate(date);
      this.getOpportunities(20, userOpportunityIds, void 0, dateAppointmentIds).then((opportunities) => {
        res(opportunities);
      }).catch(() => {
        rej();
      });
    }));
  }
  getOpportunityIdsForDateRange(startDate, endDate) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunities"], "readonly");
      const store = transaction.objectStore("opportunities");
      const index = store.index("byScheduled");
      const lowerBound = /* @__PURE__ */ new Date(`${startDate}T00:00:00`);
      const upperBound = /* @__PURE__ */ new Date(`${endDate}T23:59:59`);
      const range = IDBKeyRange.bound(lowerBound, upperBound);
      const request = index.getAllKeys(range);
      const matchingIds = [];
      request.onsuccess = function(event) {
        matchingIds.push(...this.result);
        res(matchingIds);
      };
      request.onerror = (event) => {
        rej();
      };
    }));
  }
  getOpportunitiesForDateRange(startDate, endDate, userId = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let userOpportunityIds;
      if (userId != null) {
        userOpportunityIds = yield this.getOpportunityIdsForUser(userId);
      }
      const dateOpportunityIds = yield this.getOpportunityIdsForDateRange(startDate, endDate);
      this.getOpportunities(20, userOpportunityIds, void 0, dateOpportunityIds).then((appointments) => {
        res(appointments);
      }).catch(() => {
        rej();
      });
    }));
  }
  searchOpportunities(userId, query, status = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["opportunities"], "readonly");
      const store = transaction.objectStore("opportunities");
      const userIndex = store.index("byAssignedId");
      const cursorRequest = userIndex.openCursor(null);
      const matchingRecords = [];
      query = query.toLowerCase();
      cursorRequest.onsuccess = function(event) {
        const cursor = this.result;
        if (cursor != null) {
          const firstName = cursor.value.customer?.firstName || "";
          const lastName = cursor.value.customer?.lastName || "";
          const jobId = `${cursor.value.jobId}`;
          if ((firstName.toLowerCase().includes(query) || lastName.toLowerCase().includes(query) || jobId.includes(query)) && (status == null || cursor.value.status == status) && cursor.value.assignedTo?.id == userId) {
            matchingRecords.push(cursor.value);
          }
          cursor.continue();
        } else {
          res(matchingRecords);
        }
      };
      cursorRequest.onerror = () => {
        rej();
      };
    }));
  }
  storeOpportunity(oppToStore) {
    return new Promise((res, rej) => __async(this, null, function* () {
      const originalStored = oppToStore.lastStored;
      oppToStore.lastStored = /* @__PURE__ */ new Date();
      if (oppToStore.scheduled_Utc != null) {
        oppToStore.scheduled_Utc = oppToStore.scheduled_Utc instanceof Date ? oppToStore.scheduled_Utc : new Date(oppToStore.scheduled_Utc);
      }
      oppToStore.ledger = [];
      this.storeItem("opportunities", oppToStore).then(() => {
        res();
      }).catch(() => {
        oppToStore.lastStored = originalStored;
        rej();
      });
    }));
  }
  storeOpportunities(oppsToStore) {
    return new Promise((res, rej) => __async(this, null, function* () {
      for (var i = 0, len = oppsToStore.length; i < len; i++) {
        const opportunity = oppsToStore[i];
        yield this.storeOpportunity(opportunity);
      }
      res();
    }));
  }
  storeOpportunityFromRemote(oppToStore, reset = false) {
    return new Promise((res, rej) => __async(this, null, function* () {
      const localOpp = yield this.getItem("opportunities", oppToStore.id);
      if (localOpp != null) {
        if (localOpp.status == OpportunityStatus.Open) {
          yield this.storeOpportunity(oppToStore);
        }
      } else {
        yield this.storeOpportunity(oppToStore);
      }
      if (reset) {
        yield this.deleteLedgerEntriesForOpportunity(oppToStore.id);
      }
      for (var i = 0, len = oppToStore.ledger.length; i < len; i++) {
        const entry = oppToStore.ledger[i];
        entry.remoteStoredStatus = 1;
        entry.remoteStored_Utc = /* @__PURE__ */ new Date();
        yield this.storeOpportunityLedgerEntry(entry, false);
      }
      res();
    }));
  }
  storeOpportunitiesFromRemote(opportunities, reset = false) {
    return new Promise((res, rej) => __async(this, null, function* () {
      for (var i = 0, len = opportunities.length; i < len; i++) {
        const opportunity = opportunities[i];
        opportunity.lastSynced = /* @__PURE__ */ new Date();
        yield this.storeOpportunityFromRemote(opportunity, reset);
      }
      res();
    }));
  }
  // #endregion
  // #region Appointments
  initAppointmentStore(db, transaction) {
    const existingStore = db.objectStoreNames.contains("appointments");
    let appointmentStore;
    if (existingStore) {
      appointmentStore = transaction.objectStore("appointments");
    } else {
      appointmentStore = db.createObjectStore("appointments", {
        keyPath: "id"
      });
    }
    this.createIndex(appointmentStore, "byJobId", "jobId");
    this.createIndex(appointmentStore, "byAssignedId", "assignedTo.id");
    this.createIndex(appointmentStore, "byAssignedFirstName", "assignedTo.firstName");
    this.createIndex(appointmentStore, "byAssignedLastName", "assignedTo.lastName");
    this.createIndex(appointmentStore, "byCustomerFirstName", "customer.firstName");
    this.createIndex(appointmentStore, "byCustomerLastName", "customer.lastName");
    this.createIndex(appointmentStore, "byScheduled", "scheduled_Utc");
    this.createIndex(appointmentStore, "byLastUpdated", "lastUpdated_Utc");
    this.createIndex(appointmentStore, "byStatus", "status");
  }
  getAppointment(id) {
    return this.getItem("appointments", id);
  }
  getAppointments(limit = 20, userAppointmentIds = void 0, statusAppointmentIds = void 0, dateAppointmentIds = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["appointments"], "readonly");
      const store = transaction.objectStore("appointments");
      const scheduledIndex = store.index("byScheduled");
      const scheduledRequest = scheduledIndex.openCursor(void 0, "prev");
      const results = [];
      scheduledRequest.onsuccess = function(event) {
        const cursor = this.result;
        if (cursor != null && results.length < limit) {
          const appointment = cursor.value;
          if ((statusAppointmentIds == null || statusAppointmentIds.includes(appointment.id)) && (userAppointmentIds == null || userAppointmentIds.includes(appointment.id)) && (dateAppointmentIds == null || dateAppointmentIds.includes(appointment.id))) {
            results.push(appointment);
          }
          cursor.continue();
        } else {
          res(results);
        }
      };
      scheduledRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  getAppointmentsForUser(userId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let userAppointmentIds = yield this.getAppointmentIdsForUser(userId);
      this.getAppointments(20, userAppointmentIds, void 0, void 0).then((appointments) => {
        res(appointments);
      }).catch(() => {
        rej();
      });
    }));
  }
  getAppointmentIdsForUser(userId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["appointments"], "readonly");
      const store = transaction.objectStore("appointments");
      const userIndex = store.index("byAssignedId");
      const userRange = IDBKeyRange.only(userId);
      const userRequest = userIndex.getAllKeys(userRange);
      const matchingUserIds = [];
      userRequest.onsuccess = function(event) {
        matchingUserIds.push(...this.result);
        res(matchingUserIds);
      };
      userRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  getAppointmentIdsForStatus(status) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["appointments"], "readonly");
      const store = transaction.objectStore("appointments");
      const statusIndex = store.index("byStatus");
      const range = IDBKeyRange.only(status);
      const request = statusIndex.getAllKeys(range);
      const matchingUserIds = [];
      request.onsuccess = function(event) {
        matchingUserIds.push(...this.result);
        res(matchingUserIds);
      };
      request.onerror = (event) => {
        rej();
      };
    }));
  }
  getAppointmentsForStatus(status, userId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      const userAppointmentIds = yield this.getAppointmentIdsForUser(userId);
      const statusAppointmentIds = yield this.getAppointmentIdsForStatus(status);
      this.getAppointments(20, userAppointmentIds, statusAppointmentIds).then((appointments) => {
        res(appointments);
      }).catch(() => {
        rej();
      });
    }));
  }
  getAppointmentIdsForDate(date) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["appointments"], "readonly");
      const store = transaction.objectStore("appointments");
      const index = store.index("byScheduled");
      const lowerBound = /* @__PURE__ */ new Date(`${date}T00:00:00`);
      const upperBound = /* @__PURE__ */ new Date(`${date}T23:59:59`);
      const range = IDBKeyRange.bound(lowerBound, upperBound);
      const request = index.getAllKeys(range);
      const matchingIds = [];
      request.onsuccess = function(event) {
        matchingIds.push(...this.result);
        res(matchingIds);
      };
      request.onerror = (event) => {
        rej();
      };
    }));
  }
  getAppointmentsForDate(date, userId = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let userAppointmentIds;
      if (userId != null) {
        userAppointmentIds = yield this.getAppointmentIdsForUser(userId);
      }
      const dateAppointmentIds = yield this.getAppointmentIdsForDate(date);
      this.getAppointments(20, userAppointmentIds, void 0, dateAppointmentIds).then((appointments) => {
        res(appointments);
      }).catch(() => {
        rej();
      });
    }));
  }
  getAppointmentIdsForDateRange(startDate, endDate) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["appointments"], "readonly");
      const store = transaction.objectStore("appointments");
      const index = store.index("byScheduled");
      const lowerBound = /* @__PURE__ */ new Date(`${startDate}T00:00:00`);
      const upperBound = /* @__PURE__ */ new Date(`${endDate}T23:59:59`);
      const range = IDBKeyRange.bound(lowerBound, upperBound);
      const request = index.getAllKeys(range);
      const matchingIds = [];
      request.onsuccess = function(event) {
        matchingIds.push(...this.result);
        res(matchingIds);
      };
      request.onerror = (event) => {
        rej();
      };
    }));
  }
  getAppointmentsForDateRange(startDate, endDate, userId = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let userAppointmentIds;
      if (userId != null) {
        userAppointmentIds = yield this.getAppointmentIdsForUser(userId);
      }
      const dateAppointmentIds = yield this.getAppointmentIdsForDateRange(startDate, endDate);
      this.getAppointments(20, userAppointmentIds, void 0, dateAppointmentIds).then((appointments) => {
        res(appointments);
      }).catch(() => {
        rej();
      });
    }));
  }
  searchAppointments(userId, query, status = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["appointments"], "readonly");
      const store = transaction.objectStore("appointments");
      const userIndex = store.index("byAssignedId");
      const cursorRequest = userIndex.openCursor(null);
      const matchingRecords = [];
      query = query.toLowerCase();
      cursorRequest.onsuccess = function(event) {
        const cursor = this.result;
        if (cursor != null) {
          const firstName = cursor.value.customer?.firstName || "";
          const lastName = cursor.value.customer?.lastName || "";
          const jobId = `${cursor.value.jobId}`;
          if ((firstName.toLowerCase().includes(query) || lastName.toLowerCase().includes(query) || jobId.includes(query)) && (status == null || cursor.value.status == status) && cursor.value.assignedTo?.id == userId) {
            matchingRecords.push(cursor.value);
          }
          cursor.continue();
        } else {
          res(matchingRecords);
        }
      };
      cursorRequest.onerror = () => {
        rej();
      };
    }));
  }
  storeAppointment(appointmentToStore) {
    return new Promise((res, rej) => __async(this, null, function* () {
      const originalStored = appointmentToStore.lastStored;
      appointmentToStore.lastStored = /* @__PURE__ */ new Date();
      appointmentToStore.lastUpdated_Utc = /* @__PURE__ */ new Date();
      if (appointmentToStore.scheduled_Utc != null) {
        appointmentToStore.scheduled_Utc = appointmentToStore.scheduled_Utc instanceof Date ? appointmentToStore.scheduled_Utc : new Date(appointmentToStore.scheduled_Utc);
      }
      this.storeItem("appointments", appointmentToStore).then(() => {
        res();
      }).catch(() => {
        appointmentToStore.lastStored = originalStored;
        rej();
      });
    }));
  }
  storeAppointments(appointments) {
    return new Promise((res, rej) => __async(this, null, function* () {
      for (var i = 0, len = appointments.length; i < len; i++) {
        const appointment = appointments[i];
        yield this.storeAppointment(appointment);
      }
      res();
    }));
  }
  remoteStoreAppointment(appointmentToStore) {
    return new Promise((res, rej) => __async(this, null, function* () {
      const localAppointment = yield this.getItem("appointments", appointmentToStore.id);
      if (localAppointment != null) {
        if (localAppointment.status == AppointmentStatus.Open || localAppointment.status == AppointmentStatus.Canceled) {
          localAppointment.property = appointmentToStore.property;
          localAppointment.status = appointmentToStore.status;
          localAppointment.job = appointmentToStore.job;
          localAppointment.dispositionStatus = appointmentToStore.dispositionStatus;
          localAppointment.statusDescription = appointmentToStore.statusDescription;
          localAppointment.scheduled_Utc = appointmentToStore.scheduled_Utc;
          localAppointment.customerNeeds = appointmentToStore.customerNeeds;
          localAppointment.isEmergency = appointmentToStore.isEmergency;
          localAppointment.accepted = appointmentToStore.accepted_Utc != null;
          localAppointment.accepted_Utc = appointmentToStore.accepted_Utc;
          localAppointment.availableDocuments = appointmentToStore.availableDocuments;
          localAppointment.availableEntities = appointmentToStore.availableEntities;
          localAppointment.availableFixtures = appointmentToStore.availableFixtures;
          localAppointment.availablePaymentSettings = appointmentToStore.availablePaymentSettings;
          localAppointment.availablePresentationContent = appointmentToStore.availablePresentationContent;
          localAppointment.availablePromotions = appointmentToStore.availablePromotions;
          localAppointment.availableShops = appointmentToStore.availableShops;
          localAppointment.availableSkus = appointmentToStore.availableSkus;
        }
        if (localAppointment.documents != null && appointmentToStore.documents != null) {
          appointmentToStore.documents.forEach((document) => {
            const localDocument = localAppointment.documents.find((x) => x.id == document.id);
            if (localDocument != null) {
              document.signers.forEach((signer) => {
                const existingSigner = localDocument.signers.find((x) => x.fullName == signer.fullName);
                if (existingSigner == null) {
                  localDocument.signers.push(signer);
                }
              });
              document.signatures.forEach((signature) => {
                const existingSignature = localDocument.signatures.find((x) => x.fieldId == signature.fieldId);
                if (existingSignature != null) {
                  if (existingSignature.timeStamp_Utc == null || signature.timeStamp_Utc != null && existingSignature.timeStamp_Utc < signature.timeStamp_Utc) {
                    existingSignature.signature_B64 = signature.signature_B64;
                    existingSignature.appVersion = signature.appVersion;
                    existingSignature.device = signature.device;
                    existingSignature.hash_SHA256 = signature.hash_SHA256;
                    existingSignature.hashedId = signature.hashedId;
                    existingSignature.latlng = signature.latlng;
                    existingSignature.ip = signature.ip;
                    existingSignature.signerName = signature.signerName;
                    existingSignature.timeStamp_Utc = signature.timeStamp_Utc;
                  }
                } else {
                  localDocument.signatures.push(signature);
                }
              });
            } else {
              localAppointment.documents.push(document);
            }
          });
        }
        this.storeAppointment(localAppointment);
        res();
      } else {
        this.storeAppointment(appointmentToStore);
        res();
      }
    }));
  }
  remoteStoreAppointments(appointments) {
    return new Promise((res, rej) => __async(this, null, function* () {
      for (var i = 0, len = appointments.length; i < len; i++) {
        const appointment = appointments[i];
        yield this.remoteStoreAppointment(appointment);
      }
      res();
    }));
  }
  // #endregion
  // #region Mapping
  initMappingStore(db, transaction) {
    const existingStore = db.objectStoreNames.contains("address_coordinates");
    let mappingStore;
    if (existingStore) {
      mappingStore = transaction.objectStore("address_coordinates");
    } else {
      mappingStore = db.createObjectStore("address_coordinates", {
        keyPath: "encodedAddress"
      });
    }
    this.createIndex(mappingStore, "byDisplayName", "display_name");
    this.createIndex(mappingStore, "byType", "type");
    this.createIndex(mappingStore, "byLatitude", "lat");
    this.createIndex(mappingStore, "byLongitude", "lon");
  }
  getAddressCoordinates(encodedAddress) {
    return this.getItem("address_coordinates", encodedAddress);
  }
  storeAddressCoordinates(coordinates) {
    return this.storeItem("address_coordinates", coordinates);
  }
  // #endregion
  // #region Presentations
  initPresentationStore(db, transaction) {
    const existingStore = db.objectStoreNames.contains("presentations");
    let presentationStore;
    if (existingStore) {
      presentationStore = transaction.objectStore("presentations");
    } else {
      presentationStore = db.createObjectStore("presentations", {
        keyPath: "id"
      });
    }
    this.createIndex(presentationStore, "byName", "name");
    this.createIndex(presentationStore, "byCreated", "created_Utc");
    this.createIndex(presentationStore, "byLastUpdated", "lastUpdated_Utc");
    this.createIndex(presentationStore, "byUserId", "userId");
  }
  getPresentationByName(userId, name) {
    return new Promise((res, rej) => {
      this.getPresentationsForUser(userId).then((presentations) => {
        const matchingPresentation = presentations.find((x) => x.name == name);
        res(matchingPresentation);
      }).catch(() => {
        rej();
      });
    });
  }
  getPresentations(limit = 99, presentationIds = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["presentations"], "readonly");
      const store = transaction.objectStore("presentations");
      const scheduledIndex = store.index("byLastUpdated");
      const scheduledRequest = scheduledIndex.openCursor(void 0, "prev");
      const results = [];
      scheduledRequest.onsuccess = function(event) {
        const cursor = this.result;
        if (cursor != null && results.length < limit) {
          const reminder = cursor.value;
          if (presentationIds == null || presentationIds.includes(reminder.id)) {
            results.push(reminder);
          }
          cursor.continue();
        } else {
          res(results);
        }
      };
      scheduledRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  getPresentationsForUser(userId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let presentationIds = yield this.getPresentationIdsForUser(userId);
      this.getPresentations(999, presentationIds).then((presentations) => {
        res(presentations);
      }).catch(() => {
        rej();
      });
    }));
  }
  getPresentationIdsForUser(userId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["presentations"], "readonly");
      const store = transaction.objectStore("presentations");
      const userIndex = store.index("byUserId");
      const userRange = IDBKeyRange.only(userId);
      const userRequest = userIndex.getAllKeys(userRange);
      const matchingUserIds = [];
      userRequest.onsuccess = function(event) {
        matchingUserIds.push(...this.result);
        res(matchingUserIds);
      };
      userRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  storePresentation(presentation) {
    return this.storeItem("presentations", presentation);
  }
  // #endregion
  // #region Reminders
  initReminderStore(db, transaction) {
    const existingStore = db.objectStoreNames.contains("reminders");
    let reminderStore;
    if (existingStore) {
      reminderStore = transaction.objectStore("reminders");
    } else {
      reminderStore = db.createObjectStore("reminders", {
        keyPath: "id"
      });
    }
    this.createIndex(reminderStore, "byCreatorId", "creator.id");
    this.createIndex(reminderStore, "byCreatorFirstName", "creator.firstName");
    this.createIndex(reminderStore, "byCreatorLastName", "creator.lastName");
    this.createIndex(reminderStore, "byRecipientId", "recipient.id");
    this.createIndex(reminderStore, "byRecipientFirstName", "recipient.firstName");
    this.createIndex(reminderStore, "byRecipientLastName", "recipient.lastName");
    this.createIndex(reminderStore, "byTitle", "title");
    this.createIndex(reminderStore, "byNote", "note");
    this.createIndex(reminderStore, "byScheduled", "scheduled_Utc");
    this.createIndex(reminderStore, "byReference", "reference.id");
    this.createIndex(reminderStore, "byLastUpdated", "lastUpdated_Utc");
    this.createIndex(reminderStore, "byPriority", "priority");
    this.createIndex(reminderStore, "byStatus", "status");
  }
  getReminders(limit = 99, userReminderIds = void 0) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["reminders"], "readonly");
      const store = transaction.objectStore("reminders");
      const scheduledIndex = store.index("byScheduled");
      const scheduledRequest = scheduledIndex.openCursor(void 0, "prev");
      const results = [];
      scheduledRequest.onsuccess = function(event) {
        const cursor = this.result;
        if (cursor != null && results.length < limit) {
          const reminder = cursor.value;
          if (userReminderIds == null || userReminderIds.includes(reminder.id)) {
            results.push(reminder);
          }
          cursor.continue();
        } else {
          res(results);
        }
      };
      scheduledRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  getRemindersForUser(userId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      let userReminderIds = yield this.getReminderIdsForUser(userId);
      this.getReminders(999, userReminderIds).then((reminders) => {
        res(reminders);
      }).catch(() => {
        rej();
      });
    }));
  }
  getReminderIdsForUser(userId) {
    return new Promise((res, rej) => __async(this, null, function* () {
      this.db = yield this.ensureDb();
      const transaction = this.db.transaction(["reminders"], "readonly");
      const store = transaction.objectStore("reminders");
      const userIndex = store.index("byRecipientId");
      const userRange = IDBKeyRange.only(userId);
      const userRequest = userIndex.getAllKeys(userRange);
      const matchingUserIds = [];
      userRequest.onsuccess = function(event) {
        matchingUserIds.push(...this.result);
        res(matchingUserIds);
      };
      userRequest.onerror = (event) => {
        rej();
      };
    }));
  }
  storeReminder(reminder) {
    if (reminder.scheduled_Utc != null) {
      reminder.scheduled_Utc = reminder.scheduled_Utc instanceof Date ? reminder.scheduled_Utc : new Date(reminder.scheduled_Utc);
    }
    if (reminder.lastUpdated_Utc != null) {
      reminder.lastUpdated_Utc = reminder.lastUpdated_Utc instanceof Date ? reminder.lastUpdated_Utc : new Date(reminder.lastUpdated_Utc);
    }
    return this.storeItem("reminders", reminder);
  }
  remoteStoreReminders(reminders) {
    return new Promise((res, rej) => __async(this, null, function* () {
      for (var i = 0, len = reminders.length; i < len; i++) {
        const reminder = reminders[i];
        yield this.storeReminder(reminder);
      }
      res();
    }));
  }
  // #endregion
  // #region Commission Estimates
  initCommissionStore(db, transaction) {
    const existingStore = db.objectStoreNames.contains("commission_estimates");
    let commissionStore;
    if (existingStore) {
      commissionStore = transaction.objectStore("commission_estimates");
    } else {
      commissionStore = db.createObjectStore("commission_estimates", {
        keyPath: "id"
      });
    }
    this.createIndex(commissionStore, "byStartDate", "startDate");
    this.createIndex(commissionStore, "byEndDate", "endDate");
    this.createIndex(commissionStore, "byTotalCommission", "totalNetCommission");
  }
  storeCommissionEstimate(estimate) {
    return this.storeItem("commission_estimates", estimate);
  }
  // #endregion
  // #region Initialization
  initializeDb() {
    return __async(this, null, function* () {
      try {
        this.db = yield this.openDb();
      } catch (error) {
        console.error("Failed to initialize database:", error);
        throw new Error("Database initialization failed");
      }
    });
  }
  initializeStores(db, transaction) {
    if (transaction != null) {
      try {
        db.deleteObjectStore("repipeStore");
      } catch {
        console.warn("No repipeStore found in database.");
      }
      this.initAppointmentStore(db, transaction);
      this.initOpportunityStore(db, transaction);
      this.initOpportunityLedgerStore(db, transaction);
      this.initReminderStore(db, transaction);
      this.initPresentationStore(db, transaction);
      this.initCommissionStore(db, transaction);
      this.initUserStore(db, transaction);
      this.initMappingStore(db, transaction);
      this.initImageStore(db, transaction);
    }
  }
  createIndex(store, indexName, property, unique = false) {
    try {
      store.createIndex(indexName, property, { unique });
    } catch (e) {
    }
  }
  openDb() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 23);
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        const transaction = event.target.transaction;
        this.initializeStores(db, transaction);
      };
      request.onsuccess = (event) => {
        resolve(event.target.result);
      };
      request.onerror = () => reject(new Error("Failed to open IndexedDB"));
    });
  }
  ensureDb() {
    return __async(this, null, function* () {
      if (!this.db) {
        this.db = yield this.openDb();
      }
      return this.db;
    });
  }
  // #endregion
  // #region Retrieval
  getRawItem(storeName, key) {
    return __async(this, null, function* () {
      const db = yield this.ensureDb();
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(storeName, "readonly");
        const store = transaction.objectStore(storeName);
        const request = store.get(key);
        request.onsuccess = () => resolve(request.result ?? null);
        request.onerror = () => reject(new Error(`Failed to get ${key}`));
      });
    });
  }
  getItem(storeName, key) {
    return __async(this, null, function* () {
      const value = yield this.getRawItem(storeName, key);
      if (value === null)
        return null;
      try {
        return JSON.parse(value);
      } catch {
        return value;
      }
    });
  }
  getItems(storeName, prefix) {
    return __async(this, null, function* () {
      const db = yield this.ensureDb();
      return new Promise((resolve, reject) => {
        const results = [];
        const transaction = db.transaction(storeName, "readonly");
        const store = transaction.objectStore(storeName);
        const request = store.openCursor();
        request.onsuccess = (event) => {
          const cursor = event.target.result;
          if (cursor) {
            const key = cursor.key;
            if (key.startsWith(prefix)) {
              let value;
              try {
                value = JSON.parse(cursor.value);
              } catch {
                value = cursor.value;
              }
              results.push(value);
            }
            cursor.continue();
          } else {
            resolve(results);
          }
        };
        request.onerror = () => reject(new Error(`Failed to retrieve items with prefix ${prefix}`));
      });
    });
  }
  // #endregion
  // #region Storage
  storeItem(storeName, data, clone = true) {
    return __async(this, null, function* () {
      const storableData = clone ? deepClone(data) : data;
      const db = yield this.ensureDb();
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(storeName, "readwrite");
        const store = transaction.objectStore(storeName);
        const request = store.put(storableData);
        request.onsuccess = () => resolve();
        request.onerror = () => reject(new Error(`Failed to store ${data.id}`));
      });
    });
  }
  // #endregion
  // #region Deletion
  deleteItem(storeName, key) {
    return __async(this, null, function* () {
      const db = yield this.ensureDb();
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(storeName, "readwrite");
        const store = transaction.objectStore(storeName);
        const request = store.delete(key);
        request.onsuccess = () => resolve();
        request.onerror = () => reject(new Error(`Failed to delete ${key}`));
      });
    });
  }
  clearStore(storeName) {
    return __async(this, null, function* () {
      const db = yield this.ensureDb();
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(storeName, "readwrite");
        const store = transaction.objectStore(storeName);
        const request = store.clear();
        request.onsuccess = () => resolve();
        request.onerror = () => reject(new Error("Failed to clear store"));
      });
    });
  }
  // #endregion
  closeDb() {
    if (this.db) {
      this.db.close();
      this.db = null;
    }
  }
};
_StorageService.\u0275fac = function StorageService_Factory(__ngFactoryType__) {
  return new (__ngFactoryType__ || _StorageService)(\u0275\u0275inject(HttpClient));
};
_StorageService.\u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _StorageService, factory: _StorageService.\u0275fac });
var StorageService = _StorageService;
(() => {
  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(StorageService, [{
    type: Injectable
  }], () => [{ type: HttpClient }], null);
})();

// src/app/shared/authentication/services/auth-ref.service.ts
var _AuthRefService = class _AuthRefService {
  constructor(http, storageService) {
    this.http = http;
    this.storageService = storageService;
    this.authenticated = new BehaviorSubject(false);
    this.authenticating = new BehaviorSubject(true);
    this.user = new BehaviorSubject(null);
  }
  // #region User
  setUser(user) {
    return __async(this, null, function* () {
      localStorage.setItem("rp_uid", user.id);
      this.user.next(user);
      yield this.storeUser(user);
    });
  }
  unsetUser() {
    return __async(this, null, function* () {
      localStorage.removeItem("rp_uid");
      const activeUser = this.user.getValue();
      if (activeUser != null) {
        this.deleteUser(activeUser.id);
      }
    });
  }
  getUser(id) {
    return __async(this, null, function* () {
      return this.storageService.getUser(id);
    });
  }
  deleteUser(id) {
    return __async(this, null, function* () {
      yield this.storageService.deleteUser(id);
    });
  }
  storeUser(user) {
    return __async(this, null, function* () {
      yield this.storageService.storeUser(user);
    });
  }
  setAuthenticated(authenticated) {
    this.authenticated.next(authenticated);
  }
  setAuthenticating(authenticating) {
    this.authenticating.next(authenticating);
  }
  setTokens(accessToken, expirationInMs, user = void 0) {
    return __async(this, null, function* () {
      localStorage.setItem("rp_at", accessToken);
      localStorage.setItem("rp_at_exp", expirationInMs.toString());
      if (user != null) {
        yield this.setUser(user);
      }
      this.accessToken = accessToken;
      this.accessTokenExpirationInMs = expirationInMs;
      this.accessTokenTimestamp = new Date(expirationInMs);
    });
  }
  revokeTokens() {
    localStorage.removeItem("rp_at");
    localStorage.removeItem("rp_at_exp");
    this.unsetUser();
  }
  initTokens() {
    return __async(this, null, function* () {
      const accessToken = localStorage.getItem("rp_at");
      const expirationInMsStr = localStorage.getItem("rp_at_exp");
      const userId = localStorage.getItem("rp_uid");
      try {
        if (accessToken != null && expirationInMsStr != null && userId != null) {
          const now = /* @__PURE__ */ new Date();
          const nowInMs = now.getTime();
          const accessTokenExpirationInMs = parseInt(expirationInMsStr);
          const user = yield this.getUser(userId);
          if (nowInMs < accessTokenExpirationInMs && user != null) {
            this.setTokens(accessToken, accessTokenExpirationInMs);
            this.user.next(user);
          } else {
            this.revokeTokens();
          }
        } else {
          this.revokeTokens();
        }
      } catch {
        this.revokeTokens();
      }
    });
  }
  // #endregion
  ngOnDestroy() {
  }
};
_AuthRefService.\u0275fac = function AuthRefService_Factory(__ngFactoryType__) {
  return new (__ngFactoryType__ || _AuthRefService)(\u0275\u0275inject(HttpClient), \u0275\u0275inject(StorageService));
};
_AuthRefService.\u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _AuthRefService, factory: _AuthRefService.\u0275fac, providedIn: "root" });
var AuthRefService = _AuthRefService;
(() => {
  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(AuthRefService, [{
    type: Injectable,
    args: [{
      providedIn: "root"
    }]
  }], () => [{ type: HttpClient }, { type: StorageService }], null);
})();

export {
  OpportunityPrice,
  OpportunityLineItem,
  OpportunityDocument,
  Opportunity,
  OpportunityStatus,
  OpportunityProperty,
  OpportunityPropertyEntity,
  OpportunityFixture,
  NoteAudience,
  ExportedOpportunity,
  StorageService,
  AuthRefService
};
//# sourceMappingURL=chunk-M3LFBN7N.js.map
