import categoryData from "@/content/taxonomies/category.json"; import destinationData from "@/content/taxonomies/destination.json"; import tagData from "@/content/taxonomies/tag.json"; export type TaxonomyItem = { slug: string; name: string; parent?: string; description?: string; }; export type TaxonomyMap = Map; export type TaxonomyType = "category" | "destination" | "tag"; function isTaxonomyItem(value: unknown): value is TaxonomyItem { if (typeof value !== "object" || value === null) return false; const v = value as Record; return ( typeof v.slug === "string" && typeof v.name === "string" && (v.parent === undefined || typeof v.parent === "string") && (v.description === undefined || typeof v.description === "string") ); } function buildMap(data: TaxonomyItem[]): TaxonomyMap { if (!Array.isArray(data)) { throw new Error(`[taxonomy] expected an array but received ${typeof data}`); } return new Map( data.map((item, i) => { if (!isTaxonomyItem(item)) { throw new Error( `[taxonomy] invalid item at index ${i}: ${JSON.stringify(item)}`, ); } return [item.slug, item]; }), ); } const taxonomies: Record = { category: buildMap(categoryData as TaxonomyItem[]), destination: buildMap(destinationData as TaxonomyItem[]), tag: buildMap(tagData as TaxonomyItem[]), }; export function getTaxonomyMap(type: TaxonomyType): TaxonomyMap { if (!(type in taxonomies)) { throw new Error( `[taxonomy] unknown taxonomy type "${type}". Expected one of: ${Object.keys(taxonomies).join(", ")}`, ); } return taxonomies[type]; } function buildHierarchicalPath( slug: string, map: TaxonomyMap, visited = new Set(), ): string { if (visited.has(slug)) return slug; // cycle protection visited.add(slug); const item = map.get(slug); if (!item?.parent) return slug; return `${buildHierarchicalPath(item.parent, map, visited)}/${slug}`; } export function getTaxonomyPath(type: TaxonomyType, slug: string): string { return buildHierarchicalPath(slug, getTaxonomyMap(type)); } export function getTaxonomyItem( type: TaxonomyType, slug: string, ): TaxonomyItem | undefined { return getTaxonomyMap(type).get(slug); } export function getDescendantSlugs( taxonomyType: string, slug: string, ): string[] { const map = getTaxonomyMap(taxonomyType); const result: string[] = []; for (const [key, item] of map.entries()) { if (key === slug) continue; // Đi ngược từ node này lên root, kiểm tra xem có qua slug không let current: TaxonomyItem | undefined = item; while (current?.parent) { if (current.parent === slug) { result.push(key); break; } current = map.get(current.parent); } } return result; }