/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

/**
 * Rust Code Generator
 *
 * Generates Rust types from EXPRESS schema for:
 * - Type IDs (CRC32 constants)
 * - IfcType enum with all variants
 * - Geometry category classification
 * - Type conversion functions
 */

import type { ExpressSchema, EntityDefinition } from './express-parser.js';
import { crc32 } from './crc32.js';
import { getInheritanceChain } from './express-parser.js';

export interface RustGeneratedCode {
  typeIds: string;
  schema: string;
}

/**
 * Generate all Rust code from EXPRESS schema
 */
export function generateRust(schema: ExpressSchema): RustGeneratedCode {
  return {
    typeIds: generateTypeIdConstants(schema),
    schema: generateIfcTypeEnum(schema),
  };
}

/**
 * Generate CRC32 type ID constants
 */
function generateTypeIdConstants(schema: ExpressSchema): string {
  let code = `// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Auto-generated IFC Type ID Constants
//!
//! CRC32 hashes for fast type identification.
//! Generated from EXPRESS schema: ${schema.name}
//!
//! DO NOT EDIT - This file is auto-generated by @ifc-lite/codegen

#![allow(dead_code)]

`;

  // Group by category for readability
  const categories = categorizeEntities(schema);

  for (const [category, entities] of Object.entries(categories)) {
    code += `// ${category}\n`;
    for (const entity of entities) {
      const id = crc32(entity.name);
      code += `pub const ${entity.name.toUpperCase()}: u32 = ${id};\n`;
    }
    code += '\n';
  }

  return code;
}

/**
 * Generate the main IfcType enum
 */
function generateIfcTypeEnum(schema: ExpressSchema): string {
  let code = `// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Auto-generated IFC Type Enum
//!
//! Generated from EXPRESS schema: ${schema.name}
//!
//! DO NOT EDIT - This file is auto-generated by @ifc-lite/codegen

use std::fmt;

/// IFC Entity Types
///
/// All ${schema.entities.length} entity types from the ${schema.name} schema.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IfcType {
`;

  // Group entities by category
  const categories = categorizeEntities(schema);

  for (const [category, entities] of Object.entries(categories)) {
    code += `    // ${category}\n`;
    for (const entity of entities) {
      if (entity.isAbstract) {
        code += `    /// Abstract entity\n`;
      }
      code += `    ${entity.name},\n`;
    }
    code += '\n';
  }

  // Add Unknown variant for unrecognized types
  code += `    /// Unknown/unrecognized IFC type (stores CRC32 hash)
    Unknown(u32),
}

impl IfcType {
    /// Parse IFC type from string (case-insensitive)
    pub fn from_str(s: &str) -> Self {
        let upper = s.to_uppercase();
        match upper.as_str() {
`;

  // Generate match arms for all entities
  for (const entity of schema.entities) {
    code += `            "${entity.name.toUpperCase()}" => Self::${entity.name},\n`;
  }

  code += `            _ => Self::Unknown(crc32_hash(&upper)),
        }
    }

    /// Parse from CRC32 type ID
    pub fn from_id(id: u32) -> Self {
        match id {
`;

  // Generate match arms for CRC32 IDs
  for (const entity of schema.entities) {
    const id = crc32(entity.name);
    code += `            ${id} => Self::${entity.name},\n`;
  }

  code += `            _ => Self::Unknown(id),
        }
    }

    /// Get CRC32 type ID
    pub fn id(&self) -> u32 {
        match self {
`;

  for (const entity of schema.entities) {
    const id = crc32(entity.name);
    code += `            Self::${entity.name} => ${id},\n`;
  }

  code += `            Self::Unknown(id) => *id,
        }
    }

    /// Get string representation (uppercase)
    pub fn as_str(&self) -> &'static str {
        match self {
`;

  for (const entity of schema.entities) {
    code += `            Self::${entity.name} => "${entity.name.toUpperCase()}",\n`;
  }

  code += `            Self::Unknown(_) => "UNKNOWN",
        }
    }

    /// Get display name (PascalCase)
    pub fn name(&self) -> &'static str {
        match self {
`;

  for (const entity of schema.entities) {
    code += `            Self::${entity.name} => "${entity.name}",\n`;
  }

  code += `            Self::Unknown(_) => "Unknown",
        }
    }

    /// Get parent type (if any)
    pub fn parent(&self) -> Option<Self> {
        match self {
`;

  for (const entity of schema.entities) {
    if (entity.supertype) {
      code += `            Self::${entity.name} => Some(Self::${entity.supertype}),\n`;
    }
  }

  code += `            _ => None,
        }
    }

    /// Check if this type is a subtype of another
    pub fn is_subtype_of(&self, parent: Self) -> bool {
        let mut current = Some(*self);
        while let Some(t) = current {
            if t == parent {
                return true;
            }
            current = t.parent();
        }
        false
    }

    /// Check if this is an abstract type
    pub fn is_abstract(&self) -> bool {
        match self {
`;

  const abstractTypes = schema.entities.filter((e) => e.isAbstract);
  for (const entity of abstractTypes) {
    code += `            Self::${entity.name} => true,\n`;
  }

  code += `            _ => false,
        }
    }
}

impl fmt::Display for IfcType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.name())
    }
}

/// CRC32 hash function for unknown types
fn crc32_hash(s: &str) -> u32 {
    const TABLE: [u32; 256] = [
        0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
        0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
        0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
        0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
        0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
        0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
        0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
        0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
        0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
        0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
        0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
        0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
        0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
        0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
        0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
        0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
        0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
        0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
        0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cd9, 0x5005713c, 0x270241aa,
        0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
        0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
        0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
        0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
        0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
        0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
        0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
        0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
        0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
        0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
        0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
        0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
        0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
        0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
        0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
        0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
        0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
        0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
        0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
        0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
        0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
        0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd706b3,
        0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
        0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
    ];

    let mut crc = 0xffffffffu32;
    for byte in s.bytes() {
        crc = TABLE[((crc ^ byte as u32) & 0xff) as usize] ^ (crc >> 8);
    }
    crc ^ 0xffffffff
}
`;

  return code;
}

/**
 * Generate geometry category classification
 * REMOVED: No longer generating geometry categories (unused code)
 */
function _generateGeometryCategories_removed(schema: ExpressSchema): string {
  let code = `// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Auto-generated Geometry Category Classification
//!
//! Generated from EXPRESS schema: ${schema.name}
//!
//! DO NOT EDIT - This file is auto-generated by @ifc-lite/codegen

use super::IfcType;

/// Geometry representation categories
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GeometryCategory {
    /// Swept solids (extrusions, revolutions)
    SweptSolid,
    /// Boolean/CSG operations
    Boolean,
    /// Explicit mesh representations
    ExplicitMesh,
    /// Mapped/instanced geometry
    MappedItem,
    /// Surface models
    Surface,
    /// Curve geometry
    Curve,
    /// Other geometry types
    Other,
}

/// Profile definition categories
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProfileCategory {
    /// Parametric profiles (rectangle, circle, I-shape, etc.)
    Parametric,
    /// Arbitrary closed profiles
    Arbitrary,
    /// Composite profiles
    Composite,
}

impl IfcType {
    /// Get geometry category for this type
    pub fn geometry_category(&self) -> Option<GeometryCategory> {
        match self {
            // Swept solids
`;

  // Swept solids
  const sweptSolids = [
    'IfcExtrudedAreaSolid',
    'IfcExtrudedAreaSolidTapered',
    'IfcRevolvedAreaSolid',
    'IfcRevolvedAreaSolidTapered',
    'IfcSurfaceCurveSweptAreaSolid',
    'IfcFixedReferenceSweptAreaSolid',
    'IfcSweptDiskSolid',
    'IfcSweptDiskSolidPolygonal',
  ];
  for (const name of sweptSolids) {
    if (schema.entities.find((e) => e.name === name)) {
      code += `            Self::${name} => Some(GeometryCategory::SweptSolid),\n`;
    }
  }

  code += `
            // Boolean/CSG
`;

  // Boolean types
  const booleanTypes = [
    'IfcBooleanResult',
    'IfcBooleanClippingResult',
    'IfcCsgSolid',
    'IfcCsgPrimitive3D',
    'IfcBlock',
    'IfcSphere',
    'IfcRightCircularCone',
    'IfcRightCircularCylinder',
    'IfcRectangularPyramid',
  ];
  for (const name of booleanTypes) {
    if (schema.entities.find((e) => e.name === name)) {
      code += `            Self::${name} => Some(GeometryCategory::Boolean),\n`;
    }
  }

  code += `
            // Explicit mesh
`;

  // Explicit mesh types
  const meshTypes = [
    'IfcFacetedBrep',
    'IfcFacetedBrepWithVoids',
    'IfcAdvancedBrep',
    'IfcAdvancedBrepWithVoids',
    'IfcManifoldSolidBrep',
    'IfcTriangulatedFaceSet',
    'IfcPolygonalFaceSet',
    'IfcTriangulatedIrregularNetwork',
    'IfcTessellatedFaceSet',
    'IfcIndexedPolygonalFace',
    'IfcShellBasedSurfaceModel',
    'IfcFaceBasedSurfaceModel',
  ];
  for (const name of meshTypes) {
    if (schema.entities.find((e) => e.name === name)) {
      code += `            Self::${name} => Some(GeometryCategory::ExplicitMesh),\n`;
    }
  }

  code += `
            // Mapped items
`;

  if (schema.entities.find((e) => e.name === 'IfcMappedItem')) {
    code += `            Self::IfcMappedItem => Some(GeometryCategory::MappedItem),\n`;
  }

  code += `
            // Surfaces
`;

  const surfaceTypes = [
    'IfcBSplineSurface',
    'IfcBSplineSurfaceWithKnots',
    'IfcRationalBSplineSurfaceWithKnots',
    'IfcCylindricalSurface',
    'IfcSphericalSurface',
    'IfcToroidalSurface',
    'IfcPlane',
    'IfcCurveBoundedPlane',
    'IfcCurveBoundedSurface',
    'IfcRectangularTrimmedSurface',
    'IfcSurfaceOfLinearExtrusion',
    'IfcSurfaceOfRevolution',
  ];
  for (const name of surfaceTypes) {
    if (schema.entities.find((e) => e.name === name)) {
      code += `            Self::${name} => Some(GeometryCategory::Surface),\n`;
    }
  }

  code += `
            // Curves
`;

  const curveTypes = [
    'IfcBSplineCurve',
    'IfcBSplineCurveWithKnots',
    'IfcRationalBSplineCurveWithKnots',
    'IfcCompositeCurve',
    'IfcPolyline',
    'IfcTrimmedCurve',
    'IfcCircle',
    'IfcEllipse',
    'IfcLine',
    'IfcIndexedPolyCurve',
  ];
  for (const name of curveTypes) {
    if (schema.entities.find((e) => e.name === name)) {
      code += `            Self::${name} => Some(GeometryCategory::Curve),\n`;
    }
  }

  code += `
            _ => None,
        }
    }

    /// Get profile category for this type
    pub fn profile_category(&self) -> Option<ProfileCategory> {
        match self {
            // Parametric profiles
`;

  const parametricProfiles = [
    'IfcRectangleProfileDef',
    'IfcRectangleHollowProfileDef',
    'IfcRoundedRectangleProfileDef',
    'IfcCircleProfileDef',
    'IfcCircleHollowProfileDef',
    'IfcEllipseProfileDef',
    'IfcIShapeProfileDef',
    'IfcAsymmetricIShapeProfileDef',
    'IfcLShapeProfileDef',
    'IfcTShapeProfileDef',
    'IfcUShapeProfileDef',
    'IfcCShapeProfileDef',
    'IfcZShapeProfileDef',
    'IfcTrapeziumProfileDef',
  ];
  for (const name of parametricProfiles) {
    if (schema.entities.find((e) => e.name === name)) {
      code += `            Self::${name} => Some(ProfileCategory::Parametric),\n`;
    }
  }

  code += `
            // Arbitrary profiles
`;

  const arbitraryProfiles = [
    'IfcArbitraryClosedProfileDef',
    'IfcArbitraryProfileDefWithVoids',
    'IfcArbitraryOpenProfileDef',
    'IfcCenterLineProfileDef',
  ];
  for (const name of arbitraryProfiles) {
    if (schema.entities.find((e) => e.name === name)) {
      code += `            Self::${name} => Some(ProfileCategory::Arbitrary),\n`;
    }
  }

  code += `
            // Composite profiles
`;

  if (schema.entities.find((e) => e.name === 'IfcCompositeProfileDef')) {
    code += `            Self::IfcCompositeProfileDef => Some(ProfileCategory::Composite),\n`;
  }
  if (schema.entities.find((e) => e.name === 'IfcDerivedProfileDef')) {
    code += `            Self::IfcDerivedProfileDef => Some(ProfileCategory::Composite),\n`;
  }
  if (schema.entities.find((e) => e.name === 'IfcMirroredProfileDef')) {
    code += `            Self::IfcMirroredProfileDef => Some(ProfileCategory::Composite),\n`;
  }

  code += `
            _ => None,
        }
    }

    /// Check if this type is a spatial structure element
    pub fn is_spatial(&self) -> bool {
        matches!(
            self,
`;

  const spatialEntities = [
    'IfcProject',
    'IfcSite',
    'IfcBuilding',
    'IfcBuildingStorey',
    'IfcSpace',
    'IfcFacility',
    'IfcFacilityPart',
  ];
  const validSpatial = spatialEntities.filter((n) => schema.entities.find((e) => e.name === n));
  if (validSpatial.length > 0) {
    code += `            ${validSpatial.map((n) => `Self::${n}`).join('\n            | ')}\n`;
  } else {
    code += `            Self::Unknown(_) // No spatial types found\n`;
  }

  code += `        )
    }

    /// Check if this type can have geometry
    pub fn can_have_geometry(&self) -> bool {
        // Products can have geometry through representations
        self.is_product() && !self.is_spatial()
    }

    /// Check if this type is a product (can have placement/representation)
    pub fn is_product(&self) -> bool {
        // Check if it's a subtype of IfcProduct
        self.is_subtype_of(Self::IfcProduct) || *self == Self::IfcProduct
    }

    /// Check if this type is a relationship
    pub fn is_relationship(&self) -> bool {
        // Check if name starts with IfcRel
        self.name().starts_with("IfcRel")
    }

    /// Check if this is a building element
    pub fn is_building_element(&self) -> bool {
        let name = self.name();
        matches!(
            *self,
            Self::IfcWall | Self::IfcWallStandardCase | Self::IfcSlab |
            Self::IfcBeam | Self::IfcColumn | Self::IfcRoof |
            Self::IfcStair | Self::IfcRamp | Self::IfcRailing |
            Self::IfcPlate | Self::IfcMember | Self::IfcFooting |
            Self::IfcPile | Self::IfcCovering | Self::IfcCurtainWall |
            Self::IfcDoor | Self::IfcWindow | Self::IfcChimney |
            Self::IfcShadingDevice | Self::IfcBuildingElementProxy |
            Self::IfcBuildingElementPart
        ) || name.contains("Reinforc")
    }
}
`;

  return code;
}

/**
 * Categorize entities for organized output
 */
function categorizeEntities(
  schema: ExpressSchema
): Record<string, EntityDefinition[]> {
  const categories: Record<string, EntityDefinition[]> = {
    'Spatial Structure': [],
    'Building Elements': [],
    Openings: [],
    MEP: [],
    'Geometry Representations': [],
    'Geometry Primitives': [],
    Profiles: [],
    Curves: [],
    Surfaces: [],
    Relationships: [],
    Properties: [],
    Materials: [],
    'Presentation & Style': [],
    'Core & Common': [],
    Other: [],
  };

  for (const entity of schema.entities) {
    const name = entity.name;
    const chain = getInheritanceChain(entity, schema);

    if (
      name.includes('Site') ||
      name.includes('Building') ||
      name.includes('Storey') ||
      name.includes('Space') ||
      name.includes('Project') ||
      chain.includes('IfcSpatialStructureElement')
    ) {
      categories['Spatial Structure'].push(entity);
    } else if (
      chain.includes('IfcBuildingElement') ||
      name.includes('Wall') ||
      name.includes('Slab') ||
      name.includes('Beam') ||
      name.includes('Column')
    ) {
      categories['Building Elements'].push(entity);
    } else if (
      name.includes('Door') ||
      name.includes('Window') ||
      name.includes('Opening')
    ) {
      categories['Openings'].push(entity);
    } else if (
      name.includes('Pipe') ||
      name.includes('Duct') ||
      name.includes('Cable') ||
      chain.includes('IfcDistributionElement')
    ) {
      categories['MEP'].push(entity);
    } else if (
      name.includes('Representation') ||
      name.includes('Shape') ||
      name.includes('GeometricSet')
    ) {
      categories['Geometry Representations'].push(entity);
    } else if (
      name.includes('Solid') ||
      name.includes('Brep') ||
      name.includes('Boolean') ||
      name.includes('Csg') ||
      name.includes('FaceSet')
    ) {
      categories['Geometry Primitives'].push(entity);
    } else if (name.includes('Profile')) {
      categories['Profiles'].push(entity);
    } else if (
      name.includes('Curve') ||
      name.includes('Polyline') ||
      name.includes('Circle') ||
      name.includes('Ellipse') ||
      name.includes('Line') ||
      name.includes('Spline')
    ) {
      categories['Curves'].push(entity);
    } else if (name.includes('Surface') || name.includes('Plane')) {
      categories['Surfaces'].push(entity);
    } else if (name.startsWith('IfcRel')) {
      categories['Relationships'].push(entity);
    } else if (
      name.includes('Property') ||
      name.includes('Quantity') ||
      name.includes('Value')
    ) {
      categories['Properties'].push(entity);
    } else if (name.includes('Material')) {
      categories['Materials'].push(entity);
    } else if (
      name.includes('Style') ||
      name.includes('Colour') ||
      name.includes('Presentation')
    ) {
      categories['Presentation & Style'].push(entity);
    } else if (
      name.includes('Root') ||
      name.includes('Object') ||
      name.includes('Product') ||
      name.includes('Element') ||
      name.includes('Resource')
    ) {
      categories['Core & Common'].push(entity);
    } else {
      categories['Other'].push(entity);
    }
  }

  // Remove empty categories
  for (const key of Object.keys(categories)) {
    if (categories[key].length === 0) {
      delete categories[key];
    }
  }

  return categories;
}
