import type { StoreMenuDish } from "../../../types"; /** Normalizace pro porovnání: malá písmena, bez diakritiky, emoji, čísel položek („28." / „13A.") a interpunkce. */ export function normalizeDishName(text: string): string { return text .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .toLowerCase() .replace(/^\s*\*?\s*\d+[a-z]?\s*[.)]\s*/, '') .replace(/[^a-z0-9]+/g, ' ') .trim(); } export type MatchedPart = { /** Původní text části poznámky */ text: string, quantity: number, dish?: StoreMenuDish, }; export type NoteMatch = { parts: MatchedPart[], /** Cena v Kč dle nabídky — jen pokud byly nalezeny všechny části a mají cenu */ total?: number, }; /** Množství zapsané jako „2x burger", „2 × burger" nebo „burger 2x". */ function parseQuantity(text: string): { quantity: number, rest: string } { const prefix = text.match(/^\s*(\d+)\s*[x×]\s*(.+)$/i); if (prefix) return { quantity: Number(prefix[1]), rest: prefix[2] }; const suffix = text.match(/^(.+?)\s*[x×]?\s*(\d+)\s*[x×]\s*$/i); if (suffix) return { quantity: Number(suffix[2]), rest: suffix[1] }; return { quantity: 1, rest: text }; } /** Najde položku nabídky k textu — přesná shoda, jinak nejbližší položka obsahující text (či naopak). */ function findDish(text: string, dishes: { dish: StoreMenuDish, norm: string }[]): StoreMenuDish | undefined { const norm = normalizeDishName(text); if (norm.length < 3) return undefined; const exact = dishes.find(d => d.norm === norm); if (exact) return exact.dish; const candidates = dishes.filter(d => d.norm.length >= 3 && (d.norm.includes(norm) || norm.includes(d.norm))); // Nejbližší délkou = nejspecifičtější shoda („Kebab" vs. „Kebab box s hranolky") candidates.sort((a, b) => Math.abs(a.norm.length - norm.length) - Math.abs(b.norm.length - norm.length)); return candidates[0]?.dish; } /** * Rozpozná v poznámce člena objednávky položky z nabídky podniku („Twister speciál + 2x Hranolky") * a spočítá jejich cenu. Nejprve zkusí celou poznámku (názvy jídel mohou obsahovat „+"). */ export function matchNoteToMenu(note: string | undefined, menu: StoreMenuDish[]): NoteMatch | undefined { if (!note?.trim() || menu.length === 0) return undefined; const dishes = menu.map(dish => ({ dish, norm: normalizeDishName(dish.name) })); const whole = dishes.find(d => d.norm === normalizeDishName(note)); const parts: MatchedPart[] = whole ? [{ text: note.trim(), quantity: 1, dish: whole.dish }] : note.split('+').map(p => p.trim()).filter(Boolean).map(text => { const { quantity, rest } = parseQuantity(text); return { text, quantity, dish: findDish(rest, dishes) }; }); if (!parts.some(p => p.dish)) return undefined; const complete = parts.every(p => p.dish?.price != null); return { parts, total: complete ? parts.reduce((sum, p) => sum + p.dish!.price! * p.quantity, 0) : undefined, }; } /** Počet položek v poznámce objednávky — části oddělené „+", včetně množství („2x Hranolky" = 2). */ export function countNoteItems(note: string | undefined): number { if (!note?.trim()) return 0; return note.split('+').map(p => p.trim()).filter(Boolean) .reduce((sum, part) => sum + parseQuantity(part).quantity, 0); } /** * Části poznámky oddělené „+" (tak, jak je uživatel zapsal). Pokud celá poznámka * odpovídá jednomu jídlu z nabídky (název může obsahovat „+"), je to jediná položka. */ export function splitNoteItems(note: string | undefined, menu: StoreMenuDish[] = []): string[] { const trimmed = (note ?? '').trim(); if (trimmed && menu.some(d => normalizeDishName(d.name) === normalizeDishName(trimmed))) return [trimmed]; return trimmed.split('+').map(p => p.trim()).filter(Boolean); } /** * Odebere z poznámky jeden kus položky na daném indexu: „2x Hranolky" → „Hranolky", * jinak celou část. Vrátí novou poznámku a cenu odebraného kusu dle nabídky (Kč), je-li známa. */ export function removeNoteItem(note: string | undefined, index: number, menu: StoreMenuDish[]): { note: string, removedPrice?: number } { const parts = splitNoteItems(note, menu); const part = parts[index]; if (part == null) return { note: note ?? '' }; const { quantity, rest } = parseQuantity(part); const dishes = menu.map(dish => ({ dish, norm: normalizeDishName(dish.name) })); const dish = dishes.find(d => d.norm === normalizeDishName(part))?.dish ?? findDish(rest, dishes); if (quantity > 2) parts[index] = `${quantity - 1}x ${rest.trim()}`; else if (quantity === 2) parts[index] = rest.trim(); else parts.splice(index, 1); return { note: parts.join(' + '), removedPrice: dish?.price }; }