"use strict";
const errors = require("./errors-fb9bpDY1.cjs");
const index = require("./orgDomains-W_rFTMBa.cjs");
const dayjs = require("dayjs");
const membership = require("./membership-fZvsiGJr.cjs");
const buildCredentialPayloadForCalendar = require("./buildCredentialPayloadForCalendar-ShynKeZ5.cjs");
const getEventTypeAppData = (eventType, appId, forcedGet) => {
  const metadata = eventType.metadata;
  const appMetadata = metadata?.apps && metadata.apps[appId];
  if (appMetadata) {
    const allowDataGet2 = forcedGet ? true : appMetadata.enabled;
    return allowDataGet2 ? {
      ...appMetadata,
      // We should favor eventType's price and currency over appMetadata's price and currency
      price: eventType.price || appMetadata.price || null,
      currency: eventType.currency || appMetadata.currency || null,
      // trackingId is legacy way to store value for TRACKING_ID. So, we need to support both.
      TRACKING_ID: appMetadata.TRACKING_ID || appMetadata.trackingId || null
    } : null;
  }
  const legacyAppsData = {
    stripe: {
      enabled: !!eventType.price,
      // Price default is 0 in DB. So, it would always be non nullish.
      price: eventType.price,
      // Currency default is "usd" in DB.So, it would also be available always
      currency: eventType.currency,
      paymentOption: "ON_BOOKING"
    },
    giphy: {
      enabled: !!eventType.metadata?.giphyThankYouPage,
      thankYouPage: eventType.metadata?.giphyThankYouPage || ""
    }
  };
  const legacyAppData = legacyAppsData[appId];
  const allowDataGet = forcedGet ? true : legacyAppData?.enabled;
  return allowDataGet ? legacyAppData : null;
};
function extractHostTimezone(eventType) {
  if (eventType?.userId && eventType?.owner?.timeZone) {
    return eventType.owner.timeZone;
  } else if (eventType?.teamId) {
    if (eventType.hosts && eventType.hosts.length > 0 && eventType.hosts[0]?.user?.timeZone) {
      return eventType.hosts[0].user.timeZone;
    } else if (eventType.team?.members && eventType.team.members.length > 0 && eventType.team.members[0]?.user?.timeZone) {
      return eventType.team.members[0]?.user?.timeZone;
    }
  }
  return dayjs.tz.guess();
}
function hasExpiryTimePassed(expiresAt, timezone) {
  if (!expiresAt)
    return false;
  if (timezone) {
    const now = dayjs().tz(timezone);
    const expiration = dayjs(expiresAt).tz(timezone);
    return expiration.isBefore(now);
  } else {
    const now = dayjs();
    const expiration = dayjs(expiresAt);
    return expiration.isBefore(now);
  }
}
function isTimeBasedExpired(expiresAt, eventType) {
  const hostTimezone = extractHostTimezone(eventType);
  return hasExpiryTimePassed(expiresAt, hostTimezone);
}
function isUsageBasedExpired(usageCount, maxUsageCount) {
  if (!maxUsageCount || maxUsageCount <= 0)
    return false;
  return usageCount >= maxUsageCount;
}
function validateHashedLinkData(linkData) {
  if (isTimeBasedExpired(linkData.expiresAt, linkData.eventType)) {
    throw new Error(errors.ErrorCode.PrivateLinkExpired);
  }
  if (isUsageBasedExpired(linkData.usageCount, linkData.maxUsageCount)) {
    throw new Error(errors.ErrorCode.PrivateLinkExpired);
  }
}
const hashedLinkSelect = {
  id: true,
  link: true,
  eventTypeId: true,
  expiresAt: true,
  maxUsageCount: true,
  usageCount: true,
  eventType: {
    select: {
      userId: true,
      teamId: true,
      users: {
        select: {
          username: true,
          profiles: {
            select: {
              id: true,
              organizationId: true,
              username: true
            }
          }
        }
      },
      team: {
        select: {
          id: true,
          slug: true,
          hideBranding: true,
          parent: {
            select: {
              hideBranding: true
            }
          },
          members: {
            select: {
              user: {
                select: {
                  timeZone: true
                }
              }
            },
            take: 1
          }
        }
      },
      hosts: {
        select: {
          user: {
            select: {
              timeZone: true
            }
          }
        }
      },
      profile: {
        select: {
          user: {
            select: {
              timeZone: true
            }
          }
        }
      },
      owner: {
        select: {
          timeZone: true
        }
      }
    }
  }
};
class HashedLinkRepository {
  constructor(prismaClient) {
    this.prismaClient = prismaClient;
  }
  static create() {
    return new HashedLinkRepository(index.prisma);
  }
  async deleteLinks(eventTypeId, linksToDelete) {
    if (linksToDelete.length === 0)
      return;
    return await this.prismaClient.hashedLink.deleteMany({
      where: {
        eventTypeId,
        link: { in: linksToDelete }
      }
    });
  }
  async createLink(eventTypeId, linkData) {
    const data = {
      eventTypeId,
      link: linkData.link,
      expiresAt: linkData.expiresAt
    };
    if (linkData.maxUsageCount && Number.isFinite(linkData.maxUsageCount)) {
      data.maxUsageCount = linkData.maxUsageCount;
    }
    return await this.prismaClient.hashedLink.create({ data });
  }
  async updateLink(eventTypeId, linkData) {
    const updateData = {
      expiresAt: linkData.expiresAt
    };
    if (typeof linkData.maxUsageCount === "number" && linkData.maxUsageCount !== null) {
      updateData.maxUsageCount = linkData.maxUsageCount;
    }
    return await this.prismaClient.hashedLink.updateMany({
      where: {
        eventTypeId,
        link: linkData.link
      },
      data: updateData
    });
  }
  async findLinksByEventTypeId(eventTypeId) {
    return await this.prismaClient.hashedLink.findMany({
      where: {
        eventTypeId
      },
      select: {
        link: true,
        expiresAt: true,
        maxUsageCount: true,
        usageCount: true
      }
    });
  }
  async findLinkWithEventTypeDetails(linkId) {
    return await this.prismaClient.hashedLink.findUnique({
      where: {
        link: linkId
      },
      select: {
        id: true,
        link: true,
        expiresAt: true,
        maxUsageCount: true,
        usageCount: true,
        eventTypeId: true,
        eventType: {
          select: {
            teamId: true,
            userId: true
          }
        }
      }
    });
  }
  async findLinkWithDetails(linkId) {
    return await this.prismaClient.hashedLink.findUnique({
      where: {
        link: linkId
      },
      select: hashedLinkSelect
    });
  }
  async findLinksWithEventTypeDetails(linkIds) {
    return await this.prismaClient.hashedLink.findMany({
      where: {
        link: {
          in: linkIds
        }
      },
      select: {
        id: true,
        link: true,
        expiresAt: true,
        maxUsageCount: true,
        usageCount: true,
        eventTypeId: true,
        eventType: {
          select: {
            teamId: true,
            userId: true
          }
        }
      }
    });
  }
  async findLinkWithValidationData(linkId) {
    return await this.prismaClient.hashedLink.findUnique({
      where: {
        link: linkId
      },
      select: {
        id: true,
        expiresAt: true,
        maxUsageCount: true,
        usageCount: true,
        eventType: {
          select: {
            userId: true,
            teamId: true,
            hosts: {
              select: {
                user: {
                  select: {
                    timeZone: true
                  }
                }
              }
            },
            profile: {
              select: {
                user: {
                  select: {
                    timeZone: true
                  }
                }
              }
            },
            owner: {
              select: {
                timeZone: true
              }
            }
          }
        }
      }
    });
  }
  async incrementUsage(linkId, maxUsageCount) {
    return await this.prismaClient.hashedLink.update({
      where: {
        id: linkId,
        usageCount: { lt: maxUsageCount }
      },
      data: {
        usageCount: { increment: 1 }
      }
    });
  }
}
class MembershipService {
  constructor(membershipRepository = new membership.MembershipRepository()) {
    this.membershipRepository = membershipRepository;
  }
  /**
   * Checks the membership status of a user within a specific team.
   */
  async checkMembership(teamId, userId) {
    const membership$1 = await membership.MembershipRepository.findUniqueByUserIdAndTeamId({ teamId, userId });
    if (!membership$1 || !membership$1.accepted) {
      return {
        isMember: false,
        isAdmin: false,
        isOwner: false,
        role: void 0
      };
    }
    const { role } = membership$1;
    const isOwner = role === index.MembershipRole.OWNER;
    const isAdmin = isOwner || role === index.MembershipRole.ADMIN;
    return {
      isMember: true,
      isAdmin,
      isOwner,
      role
    };
  }
}
class HashedLinkService {
  constructor(hashedLinkRepository = HashedLinkRepository.create(), membershipService = new MembershipService()) {
    this.hashedLinkRepository = hashedLinkRepository;
    this.membershipService = membershipService;
  }
  /**
   * Normalizes link input to a consistent format
   * @param input - String link or object with link and options
   * @returns Normalized link object
   */
  normalizeLinkInput(input) {
    return typeof input === "string" ? { link: input, expiresAt: null } : {
      link: input.link,
      expiresAt: input.expiresAt ?? null,
      maxUsageCount: input.maxUsageCount
    };
  }
  /**
   * Handles multiple private links operations - orchestrates create, update, and delete
   * @param eventTypeId - The ID of the event type
   * @param multiplePrivateLinks - Array of links to create/update (can be strings or objects with expiration)
   * @param connectedMultiplePrivateLinks - Array of existing link IDs for this event type
   */
  async handleMultiplePrivateLinks({
    eventTypeId,
    multiplePrivateLinks,
    connectedMultiplePrivateLinks
  }) {
    if (!eventTypeId || eventTypeId <= 0) {
      throw new Error("Invalid event type ID");
    }
    if (!multiplePrivateLinks || multiplePrivateLinks.length === 0) {
      await this.hashedLinkRepository.deleteLinks(eventTypeId, connectedMultiplePrivateLinks);
      return;
    }
    const normalizedLinks = multiplePrivateLinks.map((input) => this.normalizeLinkInput(input));
    const currentLinks = normalizedLinks.map((l) => l.link);
    const currentLinksSet = new Set(currentLinks);
    const linksToDelete = connectedMultiplePrivateLinks.filter((link) => !currentLinksSet.has(link));
    await this.hashedLinkRepository.deleteLinks(eventTypeId, linksToDelete);
    const existingLinksSet = new Set(connectedMultiplePrivateLinks);
    for (const linkData of normalizedLinks) {
      const exists = existingLinksSet.has(linkData.link);
      if (!exists) {
        await this.hashedLinkRepository.createLink(eventTypeId, linkData);
      } else {
        await this.hashedLinkRepository.updateLink(eventTypeId, linkData);
      }
    }
  }
  /**
   * Validates a link without incrementing usage count
   * Handles both time-based and usage-based expiration with timezone awareness
   * @param linkId - The hashed link ID to validate
   * @returns The validated link data
   * @throws Error with ErrorCode.PrivateLinkExpired if link is expired or invalid
   */
  async validate(linkId) {
    if (!linkId || typeof linkId !== "string") {
      throw new Error("Invalid link ID");
    }
    const hashedLink = await this.hashedLinkRepository.findLinkWithValidationData(linkId);
    if (!hashedLink) {
      throw new Error(errors.ErrorCode.PrivateLinkExpired);
    }
    validateHashedLinkData(hashedLink);
    return hashedLink;
  }
  /**
   * Validates a link and increments usage count if valid
   * Handles both time-based and usage-based expiration with timezone awareness
   * @param linkId - The hashed link ID to validate
   * @returns The validated link data
   * @throws Error with ErrorCode.PrivateLinkExpired if link is expired or invalid
   */
  async validateAndIncrementUsage(linkId) {
    const hashedLink = await this.validate(linkId);
    if (hashedLink.expiresAt)
      return hashedLink;
    if (hashedLink.maxUsageCount && hashedLink.maxUsageCount > 0) {
      try {
        await this.hashedLinkRepository.incrementUsage(hashedLink.id, hashedLink.maxUsageCount);
      } catch (updateError) {
        throw new Error(errors.ErrorCode.PrivateLinkExpired);
      }
    }
    return hashedLink;
  }
  async findLinkWithDetails(linkId) {
    const hashedLink = await this.hashedLinkRepository.findLinkWithDetails(linkId);
    if (!hashedLink) {
      return null;
    }
    return hashedLink;
  }
  /**
   * Checks if a user has permission to access a link
   * @param link - Link object with event type information
   * @param userId - The user ID to check permissions for
   * @returns true if user has permission, false otherwise
   */
  async checkUserPermissionForLink(link, userId) {
    if (!link?.eventType || !userId || userId <= 0) {
      return false;
    }
    if (link.eventType.userId) {
      return link.eventType.userId === userId;
    }
    if (!link.eventType.teamId) {
      return false;
    }
    const membership2 = await this.membershipService.checkMembership(link.eventType.teamId, userId);
    return membership2.isMember;
  }
}
function isEventTypeColor(obj) {
  return index.eventTypeColor.safeParse(obj).success;
}
function parseEventTypeColor(obj) {
  let eventTypeColor = null;
  if (isEventTypeColor(obj))
    eventTypeColor = obj;
  return eventTypeColor;
}
const log = index.logger.getSubLogger({ prefix: ["DestinationCalendarRepository"] });
class DestinationCalendarRepository {
  static async create(data) {
    return await index.prisma.destinationCalendar.create({
      data
    });
  }
  static async createIfNotExistsForUser(data) {
    const conflictingCalendar = await DestinationCalendarRepository.findConflictingForUser(data);
    if (conflictingCalendar) {
      return conflictingCalendar;
    }
    return await index.prisma.destinationCalendar.create({
      data
    });
  }
  static async getByUserId(userId) {
    return await index.prisma.destinationCalendar.findFirst({
      where: {
        userId
      }
    });
  }
  static async getByEventTypeId(eventTypeId) {
    return await index.prisma.destinationCalendar.findFirst({
      where: {
        eventTypeId
      }
    });
  }
  static async find({ where }) {
    return await index.prisma.destinationCalendar.findFirst({
      where
    });
  }
  static async findConflictingForUser(data) {
    return await DestinationCalendarRepository.find({
      where: {
        userId: data.userId,
        integration: data.integration,
        externalId: data.externalId,
        eventTypeId: null
      }
    });
  }
  static async upsert({
    where,
    update,
    create
  }) {
    log.debug("upsert", { where, update, create });
    const credentialPayloadForUpdate = buildCredentialPayloadForCalendar.buildCredentialPayloadForPrisma({
      credentialId: update.credentialId,
      delegationCredentialId: update.delegationCredentialId
    });
    const credentialPayloadForCreate = buildCredentialPayloadForCalendar.buildCredentialPayloadForPrisma({
      credentialId: create.credentialId,
      delegationCredentialId: create.delegationCredentialId
    });
    return await index.prisma.destinationCalendar.upsert({
      where,
      update: {
        ...update,
        ...credentialPayloadForUpdate
      },
      create: {
        ...create,
        ...credentialPayloadForCreate
      }
    });
  }
}
exports.DestinationCalendarRepository = DestinationCalendarRepository;
exports.HashedLinkRepository = HashedLinkRepository;
exports.HashedLinkService = HashedLinkService;
exports.getEventTypeAppData = getEventTypeAppData;
exports.parseEventTypeColor = parseEventTypeColor;
