import { internetCatalogResponseSchema, internetPlanCatalogItemSchema, simCatalogResponseSchema, vpnCatalogResponseSchema, type InternetCatalogCollection, type InternetPlanCatalogItem, type SimCatalogCollection, type VpnCatalogCollection, type InternetPlanTemplate, type CatalogProductBase, } from "./schema.js"; import type { CatalogPriceInfo } from "./contract.js"; /** * Empty catalog defaults shared by portal and BFF. */ export const EMPTY_INTERNET_CATALOG: InternetCatalogCollection = { plans: [], installations: [], addons: [], }; export const EMPTY_SIM_CATALOG: SimCatalogCollection = { plans: [], activationFees: [], addons: [], }; export const EMPTY_VPN_CATALOG: VpnCatalogCollection = { plans: [], activationFees: [], }; /** * Safe parser helpers for catalog payloads coming from HTTP boundaries. */ export function parseInternetCatalog(data: unknown): InternetCatalogCollection { return internetCatalogResponseSchema.parse(data); } export function parseSimCatalog(data: unknown): SimCatalogCollection { return simCatalogResponseSchema.parse(data); } export function parseVpnCatalog(data: unknown): VpnCatalogCollection { return vpnCatalogResponseSchema.parse(data); } /** * Internet tier metadata map shared between BFF and portal presenters. */ const INTERNET_TIER_METADATA: Record = { silver: { tierDescription: "Simple package with broadband-modem and ISP only", description: "Simple package with broadband-modem and ISP only", features: [ "NTT modem + ISP connection", "Two ISP connection protocols: IPoE (recommended) or PPPoE", "Self-configuration of router (you provide your own)", "Monthly: ¥6,000 | One-time: ¥22,800", ], }, gold: { tierDescription: "Standard all-inclusive package with basic Wi-Fi", description: "Standard all-inclusive package with basic Wi-Fi", features: [ "NTT modem + wireless router (rental)", "ISP (IPoE) configured automatically within 24 hours", "Basic wireless router included", "Optional: TP-LINK RE650 range extender (¥500/month)", "Monthly: ¥6,500 | One-time: ¥22,800", ], }, platinum: { tierDescription: "Tailored set up with premier Wi-Fi management support", description: "Tailored set up with premier Wi-Fi management support - Recommended for homes & apartments larger than 50m²", features: [ "NTT modem + Netgear INSIGHT Wi-Fi routers", "Cloud management support for remote router management", "Automatic updates and quicker support", "Seamless wireless network setup", "Monthly: ¥6,500 | One-time: ¥22,800", "Cloud management: ¥500/month per router", ], }, }; const DEFAULT_PLAN_TEMPLATE: InternetPlanTemplate = { tierDescription: "Standard plan", description: undefined, features: undefined, }; export function getInternetTierTemplate(tier?: string | null): InternetPlanTemplate { if (!tier) { return DEFAULT_PLAN_TEMPLATE; } const normalized = tier.trim().toLowerCase(); return ( INTERNET_TIER_METADATA[normalized] ?? { tierDescription: `${tier} plan`, description: undefined, features: undefined, } ); } export type InternetInstallationTerm = "One-time" | "12-Month" | "24-Month"; export function inferInstallationTermFromSku(sku: string): InternetInstallationTerm { const normalized = sku.toLowerCase(); if (normalized.includes("24")) return "24-Month"; if (normalized.includes("12")) return "12-Month"; return "One-time"; } export type InternetAddonType = "hikari-denwa-service" | "hikari-denwa-installation" | "other"; export function inferAddonTypeFromSku(sku: string): InternetAddonType { const upperSku = sku.toUpperCase(); const isDenwa = upperSku.includes("DENWA") || upperSku.includes("HOME-PHONE") || upperSku.includes("PHONE"); if (!isDenwa) { return "other"; } const isInstallation = upperSku.includes("INSTALL") || upperSku.includes("SETUP") || upperSku.includes("ACTIVATION"); return isInstallation ? "hikari-denwa-installation" : "hikari-denwa-service"; } /** * Helper to apply tier metadata to a plan item. */ export function enrichInternetPlanMetadata(plan: InternetPlanCatalogItem): InternetPlanCatalogItem { const template = getInternetTierTemplate(plan.internetPlanTier ?? null); const existingMetadata = plan.catalogMetadata ?? {}; const metadata = { ...existingMetadata, tierDescription: existingMetadata.tierDescription ?? template.tierDescription, features: existingMetadata.features ?? template.features, isRecommended: existingMetadata.isRecommended ?? (plan.internetPlanTier?.toLowerCase() === "gold" ? true : undefined), }; return internetPlanCatalogItemSchema.parse({ ...plan, description: plan.description ?? template.description, catalogMetadata: metadata, features: plan.features ?? template.features, }); } export const internetPlanCollectionSchema = internetPlanCatalogItemSchema.array(); /** * Calculates display price information for a catalog item * Centralized logic for price formatting */ export function getCatalogProductPriceDisplay(item: CatalogProductBase): CatalogPriceInfo | null { const monthlyPrice = item.monthlyPrice ?? null; const oneTimePrice = item.oneTimePrice ?? null; const currency = "JPY"; if (monthlyPrice === null && oneTimePrice === null) { return null; } let display = ""; if (monthlyPrice !== null && monthlyPrice > 0) { display = `¥${monthlyPrice.toLocaleString()}/month`; } else if (oneTimePrice !== null && oneTimePrice > 0) { display = `¥${oneTimePrice.toLocaleString()} (one-time)`; } return { display, monthly: monthlyPrice, oneTime: oneTimePrice, currency, }; } /** * Calculate savings percentage between original and current price * Returns 0 if there are no savings (current >= original) */ export function calculateSavingsPercentage(originalPrice: number, currentPrice: number): number { if (originalPrice <= currentPrice) return 0; return Math.round(((originalPrice - currentPrice) / originalPrice) * 100); }