#!/usr/bin/env node

/**
 * Script to check naming convention for the entire project
 */

const fs = require('fs');
const path = require('path');

const SRC_DIR = path.join(__dirname, '..', 'src');
const ERRORS = [];
const WARNINGS = [];

// Conventions by layer
const CONVENTIONS = {
	// services/: camelCase + Service suffix (e.g., authService.ts)
	services: {
		folder: /^[a-z0-9]+(-[a-z0-9]+)*$/, // kebab-case
		file: /^[a-z][a-zA-Z0-9]*Service\.ts$/, // camelCase + Service.ts
		exceptions: ['index.ts', 'types.ts', 'buildCondition.ts'],
		subConventions: {
			helpers: { file: /^[a-z][a-zA-Z0-9]*Helper\.ts$/, exceptions: ['index.ts'] },
			jobs: { file: /^[a-z][a-zA-Z0-9]*Job\.ts$/, exceptions: ['index.ts'] },
			sequelize: { file: /^[a-z][a-zA-Z0-9]*\.ts$/, exceptions: ['index.ts', 'types.ts'] },
			adapters: { file: /^[A-Z][a-zA-Z0-9]*\.ts$/, exceptions: ['index.ts'] },
		},
	},
	// models/: PascalCase (e.g., User.ts)
	models: {
		folder: null, // models has no sub-folders
		file: /^[A-Z][a-zA-Z0-9]*\.ts$/, // PascalCase
		exceptions: ['index.ts', 'init-models.ts'],
	},
	// providers/: PascalCase + Provider suffix (e.g., UserProvider.ts)
	providers: {
		folder: null,
		file: /^[A-Z][a-zA-Z0-9]*Provider\.ts$/, // PascalCase + Provider.ts
		exceptions: ['index.ts'],
	},
	// interfaces/: PascalCase + I prefix (e.g., IUser.ts)
	interfaces: {
		folder: null,
		file: /^I[A-Z][a-zA-Z0-9]*\.ts$/, // PascalCase với I prefix
		exceptions: ['index.ts', 'generic.ts'],
		subConventions: {
			handler: { file: /^[a-z0-9]+(-[a-z0-9]+)*\.ts$/, exceptions: ['index.ts'] }, // kebab-case
			violations: { file: /^[A-Z][a-zA-Z0-9]*\.ts$/, exceptions: ['index.ts'] }, // PascalCase (implementations)
		},
	},
	// middlewares/: kebab-case (e.g., auth.ts, rateLimiter.ts)
	middlewares: {
		folder: null,
		file: /^[a-z0-9]+(-[a-z0-9]+)*\.ts$/, // kebab-case
		exceptions: ['index.ts'],
	},
	// utils/: camelCase (e.g., authUtils.ts)
	utils: {
		folder: null,
		file: /^[a-z][a-zA-Z0-9]*\.ts$/, // camelCase
		exceptions: ['index.ts'],
	},
	// controllers/: kebab-case cho nested folders
	controllers: {
		folder: /^[a-z0-9]+(-[a-z0-9]+)*$/, // kebab-case
		file: /^[a-z0-9]+(-[a-z0-9]+)*\.ts$/, // kebab-case
		exceptions: ['index.ts'],
	},
};

function isValidName(name, pattern) {
	if (!pattern) return true;
	return pattern.test(name);
}

function getConventionDescription(layer, subConvention) {
	if (subConvention) {
		if (subConvention.file.source === '^[a-z][a-zA-Z0-9]*Helper\\.ts$') return 'camelCase + Helper.ts';
		if (subConvention.file.source === '^[a-z][a-zA-Z0-9]*Job\\.ts$') return 'camelCase + Job.ts';
		if (subConvention.file.source === '^[a-z][a-zA-Z0-9]*\\.ts$') return 'camelCase';
		if (subConvention.file.source === '^[a-z0-9]+(-[a-z0-9]+)*\\.ts$') return 'kebab-case';
		if (subConvention.file.source === '^[A-Z][a-zA-Z0-9]*\\.ts$') return 'PascalCase';
	}
	if (layer === 'services') return 'camelCase + Service.ts';
	if (layer === 'models') return 'PascalCase';
	if (layer === 'providers') return 'PascalCase + Provider.ts';
	if (layer === 'interfaces') return 'PascalCase + I prefix';
	if (layer === 'middlewares') return 'kebab-case';
	if (layer === 'utils') return 'camelCase';
	if (layer === 'controllers') return 'kebab-case';
	return 'convention';
}

function checkNaming(directory, layer, activeConvention = null) {
	const layerConvention = CONVENTIONS[layer];
	if (!layerConvention) return;

	const convention = activeConvention || layerConvention;

	const items = fs.readdirSync(directory, { withFileTypes: true });

	for (const item of items) {
		const fullPath = path.join(directory, item.name);
		const relativePath = path.relative(SRC_DIR, fullPath);

		if (item.isDirectory()) {
			// Check folder name
			if (layerConvention.folder && !isValidName(item.name, layerConvention.folder)) {
				// Skip special folders with subConventions
				const subConv = layerConvention.subConventions && layerConvention.subConventions[item.name];
				if (subConv) {
					// Recursively check inside with sub-convention
					checkNaming(fullPath, layer, subConv);
					continue;
				}
				// Skip other special folders
				if (['docs', 'types', 'error'].includes(item.name)) {
					checkNaming(fullPath, layer);
					continue;
				}
				// Skip route parameter folders (e.g., {id}, {role_id})
				if (item.name.startsWith('{') && item.name.endsWith('}')) {
					checkNaming(fullPath, layer);
					continue;
				}
				ERRORS.push(`[NAMING] Folder "${relativePath}" does not comply with kebab-case convention`);
			} else {
				// Folder name is valid - check for subConventions
				const subConv = layerConvention.subConventions && layerConvention.subConventions[item.name];
				if (subConv) {
					checkNaming(fullPath, layer, subConv);
				} else {
					checkNaming(fullPath, layer);
				}
			}
		} else if (item.isFile() && item.name.endsWith('.ts')) {
			// Check file name
			if (convention.exceptions && convention.exceptions.includes(item.name)) continue;

			// Skip route parameter files (e.g., {id}.ts, {role_id}.ts)
			if (item.name.startsWith('{') && item.name.endsWith('}.ts')) {
				continue;
			}

			if (!isValidName(item.name, convention.file)) {
				ERRORS.push(
					`[NAMING] File "${relativePath}" does not comply with ${layer} convention (${getConventionDescription(layer, activeConvention)})`,
				);
			}
		}
	}
}

function main() {
	console.log('🔍 Checking naming convention...\n');

	// Check each layer
	const layers = Object.keys(CONVENTIONS);
	for (const layer of layers) {
		const layerPath = path.join(SRC_DIR, layer);
		if (fs.existsSync(layerPath)) {
			checkNaming(layerPath, layer);
		}
	}

	// Report results
	if (WARNINGS.length > 0) {
		console.log('⚠️  Warnings:');
		WARNINGS.forEach((w) => console.log(`  ${w}`));
		console.log();
	}

	if (ERRORS.length > 0) {
		console.log('❌ Errors:');
		ERRORS.forEach((e) => console.log(`  ${e}`));
		console.log(`\n❌ Naming convention check failed: ${ERRORS.length} error(s)`);
		process.exit(1);
	}

	console.log('✅ Naming convention check passed!');
	process.exit(0);
}

main();
