{"version":3,"sources":["../src/utils/func.ts"],"sourcesContent":["import { parse } from \"tldts\";\nimport type { CurrencyCode } from \"../types\";\n\nexport function extractDomainWithoutSuffix(domain: string) {\n  const parsedDomain = parse(domain);\n  return parsedDomain.domainWithoutSuffix;\n}\n\nexport function generateStoreSlug(domain: string): string {\n  const input = new URL(domain);\n  const parsedDomain = parse(input.href);\n  const domainName =\n    parsedDomain.domainWithoutSuffix ?? input.hostname.split(\".\")[0];\n\n  return (domainName || \"\")\n    .toLowerCase()\n    .replace(/[^a-z0-9]/g, \"-\")\n    .replace(/-+/g, \"-\")\n    .replace(/^-+|-+$/g, \"\");\n}\n\nexport const genProductSlug = ({\n  handle,\n  storeDomain,\n}: {\n  handle: string;\n  storeDomain: string;\n}) => {\n  const storeSlug = generateStoreSlug(storeDomain);\n  return `${handle}-by-${storeSlug}`;\n};\n\nexport const calculateDiscount = (\n  price: number,\n  compareAtPrice?: number\n): number =>\n  !compareAtPrice || compareAtPrice === 0\n    ? 0\n    : Math.max(\n        0,\n        Math.round(100 - (price / compareAtPrice) * 100) // Removed the decimal precision\n      );\n\n/**\n * Normalize and sanitize a domain string.\n *\n * Accepts inputs like full URLs, protocol-relative URLs, bare hostnames,\n * or strings with paths/query/fragment, and returns a normalized domain.\n *\n * Examples:\n *  - \"https://WWW.Example.com/path\" -> \"example.com\"\n *  - \"//sub.example.co.uk\" -> \"example.co.uk\"\n *  - \"www.example.com:8080\" -> \"example.com\"\n *  - \"example\" -> \"example\"\n */\nexport function sanitizeDomain(\n  input: string,\n  opts?: { stripWWW?: boolean }\n): string {\n  if (typeof input !== \"string\") {\n    throw new Error(\"sanitizeDomain: input must be a string\");\n  }\n  let raw = input.trim();\n  if (!raw) {\n    throw new Error(\"sanitizeDomain: input cannot be empty\");\n  }\n  // Only add protocol if it's missing and not protocol-relative\n  const hasProtocol = /^[a-z]+:\\/\\//i.test(raw);\n  if (!hasProtocol && !raw.startsWith(\"//\")) {\n    raw = `https://${raw}`;\n  }\n\n  const stripWWW = opts?.stripWWW ?? true;\n\n  try {\n    let url: URL;\n    if (raw.startsWith(\"//\")) {\n      url = new URL(`https:${raw}`);\n    } else if (raw.includes(\"://\")) {\n      url = new URL(raw);\n    } else {\n      url = new URL(`https://${raw}`);\n    }\n    let hostname = url.hostname.toLowerCase();\n    const hadWWW = /^www\\./i.test(url.hostname);\n    if (stripWWW) hostname = hostname.replace(/^www\\./, \"\");\n    if (!hostname.includes(\".\")) {\n      throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n    }\n    const parsed = parse(hostname);\n    if (!parsed.publicSuffix || parsed.isIcann === false) {\n      // Require a valid public suffix (e.g., TLD); reject bare hostnames\n      throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n    }\n    if (!stripWWW && hadWWW) {\n      return `www.${parsed.domain || hostname}`;\n    }\n    return parsed.domain || hostname;\n  } catch {\n    // Fallback: attempt to sanitize without URL parsing\n    let hostname = raw.toLowerCase();\n    hostname = hostname.replace(/^[a-z]+:\\/\\//, \"\"); // remove protocol if present\n    hostname = hostname.replace(/^\\/\\//, \"\"); // remove protocol-relative\n    hostname = hostname.replace(/[/:#?].*$/, \"\"); // remove path/query/fragment/port\n    const hadWWW = /^www\\./i.test(hostname);\n    if (stripWWW) hostname = hostname.replace(/^www\\./, \"\");\n    if (!hostname.includes(\".\")) {\n      throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n    }\n    const parsed = parse(hostname);\n    if (!parsed.publicSuffix || parsed.isIcann === false) {\n      throw new Error(\"sanitizeDomain: invalid domain (missing suffix)\");\n    }\n    if (!stripWWW && hadWWW) {\n      return `www.${parsed.domain || hostname}`;\n    }\n    return parsed.domain || hostname;\n  }\n}\n\n/**\n * Safely parse a date string into a Date object.\n *\n * Returns `undefined` when input is falsy or cannot be parsed into a valid date.\n * Use `|| null` at call sites that expect `null` instead of `undefined`.\n */\nexport function safeParseDate(input?: string | null): Date | undefined {\n  if (!input || typeof input !== \"string\") return undefined;\n  const d = new Date(input);\n  return Number.isNaN(d.getTime()) ? undefined : d;\n}\n\n/**\n * Normalize an option name or value to a lowercase, underscore-separated key.\n */\nexport function normalizeKey(input: string): string {\n  return input.toLowerCase().replace(/\\s+/g, \"_\");\n}\n\n/**\n * Build a map from normalized option combination → variant id strings.\n * Example key: `size#xl##color#blue`.\n */\nexport function buildVariantOptionsMap(\n  optionNames: string[],\n  variants: Array<{\n    id: number;\n    option1: string | null;\n    option2: string | null;\n    option3: string | null;\n  }>\n): Record<string, string> {\n  const keys = optionNames.map(normalizeKey);\n  const map: Record<string, string> = {};\n\n  for (const v of variants) {\n    const parts: string[] = [];\n    if (keys[0] && v.option1)\n      parts.push(`${keys[0]}#${normalizeKey(v.option1)}`);\n    if (keys[1] && v.option2)\n      parts.push(`${keys[1]}#${normalizeKey(v.option2)}`);\n    if (keys[2] && v.option3)\n      parts.push(`${keys[2]}#${normalizeKey(v.option3)}`);\n\n    if (parts.length > 0) {\n      // Ensure deterministic alphabetical ordering of parts\n      if (parts.length > 1) parts.sort();\n      const key = parts.join(\"##\");\n      const id = v.id.toString();\n      // First-write wins: do not override if key already exists\n      if (map[key] === undefined) {\n        map[key] = id;\n      }\n    }\n  }\n\n  return map;\n}\n\n/**\n * Build a normalized variant key string from an object of option name → value.\n * - Normalizes both names and values using `normalizeKey`\n * - Sorts parts alphabetically for deterministic output\n * - Joins parts using `##` and uses `name#value` for each part\n *\n * Example output: `color#blue##size#xl`\n */\nexport function buildVariantKey(\n  obj: Record<string, string | null | undefined>\n): string {\n  const parts: string[] = [];\n  for (const [name, value] of Object.entries(obj)) {\n    if (value) {\n      parts.push(`${normalizeKey(name)}#${normalizeKey(value)}`);\n    }\n  }\n  if (parts.length === 0) return \"\";\n  parts.sort((a, b) => a.localeCompare(b));\n  return parts.join(\"##\");\n}\n\n/**\n * Format a price amount (in cents) using a given ISO 4217 currency code.\n * Falls back to a simple string when Intl formatting fails.\n */\nexport function formatPrice(\n  amountInCents: number,\n  currency: CurrencyCode\n): string {\n  try {\n    return new Intl.NumberFormat(undefined, {\n      style: \"currency\",\n      currency,\n    }).format((amountInCents || 0) / 100);\n  } catch {\n    const val = (amountInCents || 0) / 100;\n    return `${val} ${currency}`;\n  }\n}\n"],"mappings":"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","names":[]}