import { meshkeeGet, tenantPath } from "@/lib/meshkee"; import { detailPath, slugifyForUrl } from "@/lib/seo-slug"; export type ApiCategory = { id: string; parentId?: string | null; name?: string | null; nameFa?: string | null; slug?: string | null; description?: string | null; sortOrder?: number | null; isActive?: boolean; }; export type NavCategory = { id: string; title: string; href: string; image?: string; children: NavCategory[]; }; function titleOf(category: Pick) { return category.nameFa?.trim() || category.name?.trim() || String(category.id); } /** Public category URL: /products/category/{id}/{nameFaSlug} */ export function categoryHref(category: Pick) { return detailPath("/products/category", category.id, titleOf(category)); } export function categoryNameSlug(title: string) { return slugifyForUrl(title); } export async function fetchProductCategories() { const data = await meshkeeGet<{ items?: ApiCategory[] }>(tenantPath("/categories?entityType=product"), { items: [], }); return (data.items ?? []).filter((category) => category.isActive !== false); } export async function fetchCategoryById(categoryId: string) { const data = await meshkeeGet<{ category?: ApiCategory }>( tenantPath(`/categories/by-id/${encodeURIComponent(categoryId)}`), {}, ); return data.category ?? null; } export function buildCategoryTree(flat: ApiCategory[]): NavCategory[] { const byId = new Map(flat.map((category) => [String(category.id), category])); const children = new Map(); for (const category of flat) { const parentId = category.parentId ? String(category.parentId) : ""; const key = parentId && byId.has(parentId) ? parentId : "root"; const list = children.get(key) ?? []; list.push(category); children.set(key, list); } const sort = (list: ApiCategory[]) => [...list].sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0) || titleOf(a).localeCompare(titleOf(b), "fa")); const build = (category: ApiCategory): NavCategory => ({ id: String(category.id), title: titleOf(category), href: categoryHref(category), children: sort(children.get(String(category.id)) ?? []).map(build), }); return sort(children.get("root") ?? []).map(build); } /** Skip single-child wrapper roots (e.g. کالاها → لوازم…) so mega menu starts at useful columns. */ export function unwrapNavRoots(nodes: NavCategory[]): NavCategory[] { let current = nodes; while (current.length === 1 && current[0].children.length > 0) { current = current[0].children; } return current; } export function flattenNavCategories(nodes: NavCategory[]): NavCategory[] { const result: NavCategory[] = []; const walk = (list: NavCategory[]) => { for (const node of list) { result.push(node); if (node.children.length) walk(node.children); } }; walk(nodes); return result; } export function findNavCategory(nodes: NavCategory[], id?: string | null) { if (!id) return undefined; return flattenNavCategories(nodes).find((item) => item.id === id); } /** * Flatten one level for the mega menu: nested groups become sibling sections * so CSS columns can pack them evenly. */ export function toMegaSections(categories: NavCategory[]): NavCategory[] { const sections: NavCategory[] = []; for (const category of categories) { const leaves = category.children.filter((child) => child.children.length === 0); const branches = category.children.filter((child) => child.children.length > 0); if (!branches.length) { sections.push(category); continue; } if (leaves.length) { sections.push({ ...category, children: leaves }); } for (const branch of branches) { sections.push(branch); } } return sections; } export async function getNavCategories() { return unwrapNavRoots(buildCategoryTree(await fetchProductCategories())); }