const mustache = require('mustache');
const nconf = require('nconf');
const { rimraf } = require('rimraf');
const { resolve } = require('path');
const { writeFile, readFile } = require('fs/promises');
const { existsSync, mkdirSync, readdirSync } = require('fs');
const { execSync } = require('child_process');
const { Sequelize } = require('sequelize');
const { SequelizeAuto } = require('sequelize-auto');

//==================== LOAD CONFIGS ===================/
const dotenv = require('dotenv');
dotenv.config({ path: resolve(__dirname, '../../../.env') });
nconf.argv().env({ separator: '__' });

const mode = nconf.get('mode') || 'full';
//==================== LOAD CONFIGS ===================/

class DatabaseGenerator {
	#message;
	#modelPath;
	#providerPath;
	#swaggerSchemaPath;
	#servicePath;
	#root;

	constructor() {
		this.#message = 'Generate Database Model';
		this.#root = resolve(__dirname, '../../../src');
		this.#providerPath = resolve(this.#root, 'providers');
		this.#swaggerSchemaPath = resolve(__dirname, '../openapi/schemas');
		this.#modelPath = `${this.#root}/models`;
		this.#servicePath = `${this.#root}/services/database`;
	}

	async executor() {
		console.time(this.#message);
		console.log('Checking database configuration...');

		/** @type {import("../../../src/interfaces/IEnv").DatabaseConfig} */
		const config = nconf.get('Database') || {
			user: process.env.DB_USER,
			password: process.env.DB_PASSWORD,
			name: process.env.DB_NAME,
			host: process.env.DB_HOST,
			port: process.env.DB_PORT,
			dialect: process.env.DB_DIALECT || 'postgres',
		};

		const forceRegen = process.argv.includes('--force') || process.argv.includes('-f');

		if (mode === 'full') {
			if (forceRegen) {
				console.log('Force regeneration enabled - will recreate all models and providers');
				await rimraf(this.#modelPath);
				await rimraf(this.#providerPath);
			} else {
				console.log('Using incremental generation - will skip existing files');
			}

			console.log(`Connecting to database: ${config.host}:${config.port}/${config.name}`);

			try {
				await this.#testConnection(config);
				console.log('Database connection successful');
				await this.#genModel(config, forceRegen);
			} catch (error) {
				console.warn('Database connection failed:', error.message);
				console.log('Continuing with existing models (if any)...');

				const modelsExist =
					existsSync(this.#modelPath) &&
					readdirSync(this.#modelPath).filter((f) => f.endsWith('.ts') && f !== 'init-models.ts').length > 0;

				if (!modelsExist) {
					console.error(
						'No existing models found and database connection failed. Please check your database configuration.',
					);
					process.exit(1);
				}

				console.log('Using existing models');
			}
		} else if (mode === 'providers-from-template') {
			await this.#genProvidersFromTemplate(forceRegen);
		} else {
			console.error(`Unknown mode: ${mode}. Use --mode=full or --mode=providers-from-template`);
			process.exit(1);
		}

		// Format generated files
		try {
			console.log('Formatting generated files...');
			// Only format the specific directories that contain generated files
			const formatPaths = [];
			if (existsSync(this.#modelPath)) formatPaths.push(`${this.#modelPath}/**/*.ts`);
			if (existsSync(this.#providerPath)) formatPaths.push(`${this.#providerPath}/**/*.ts`);

			if (formatPaths.length > 0) {
				execSync(`pnpx prettier --write ${formatPaths.join(' ')} --loglevel silent`, {
					stdio: 'pipe',
					timeout: 30000, // 30 second timeout
				});
			}
			console.log('Code formatting completed');
		} catch (error) {
			console.warn('Code formatting failed or timed out, continuing...');
		}

		console.timeEnd(this.#message);
		this.#printSummary();
	}

	/** @param {import("../../../src/interfaces/IEnv").DatabaseConfig} config */
	async #testConnection(config) {
		const testSequelize = new Sequelize({
			username: config.user,
			password: config.password,
			database: config.name,
			host: config.host,
			port: config.port,
			dialect: config.dialect,
			logging: false,
			pool: { max: 1, min: 0, acquire: 5000, idle: 1000 }, // Short timeout
		});

		try {
			await testSequelize.authenticate();
		} finally {
			await testSequelize.close();
		}
	}

	#printSummary() {
		const { readdirSync } = require('fs');

		try {
			const modelFiles = readdirSync(this.#modelPath).filter((f) => f.endsWith('.ts') && f !== 'init-models.ts');
			const providerFiles = readdirSync(this.#providerPath).filter((f) => f.endsWith('.ts'));

			console.log('\n' + '='.repeat(50));
			console.log('DATABASE GENERATION COMPLETED');
			console.log('='.repeat(50));
			console.log(`Models generated: ${modelFiles.length}`);
			console.log(`Providers generated: ${providerFiles.length}`);
			console.log(`Models location: src/models/`);
			console.log(`Providers location: src/providers/`);
			console.log('='.repeat(50));

			if (modelFiles.length > 0) {
				console.log('\nGenerated Models:');
				modelFiles.forEach((file) => console.log(`  • ${file.replace('.ts', '')}`));
			}

			if (providerFiles.length > 0) {
				console.log('\nGenerated Providers:');
				providerFiles.forEach((file) => console.log(`  • ${file.replace('.ts', '')}`));
			}

			console.log('\nNext steps:');
			console.log('  1. Review generated models and providers');
			console.log('  2. Customize providers as needed');
			console.log('  3. Update your services to use the new providers');
			console.log('  4. Test your API endpoints');
		} catch (error) {
			console.warn('Could not generate summary:', error.message);
		}
	}

	/** @param {import("../../../src/interfaces/IEnv").DatabaseConfig} db @param {boolean} forceRegen */
	async #genModel(db, forceRegen = false) {
		console.log('🔧 Generating Sequelize models...');

		if (forceRegen) {
			await rimraf(this.#modelPath);
		} else if (!existsSync(this.#modelPath)) {
			mkdirSync(this.#modelPath, { recursive: true });
		}

		/** @type {import("sequelize").Sequelize} */
		const sequelize = new Sequelize({
			username: db.user,
			password: db.password,
			database: db.name,
			host: db.host,
			port: db.port,
			dialect: db.dialect,
			logging: false,
		});

		/** @type {import("sequelize-auto").AutoOptions} */
		const options = {
			dialect: db.dialect,
			directory: this.#modelPath,
			lang: 'ts',
			useDefine: false,
			singularize: true,
			additional: { timestamps: false },
			caseFile: 'p',
			caseModel: 'p',
			caseProp: 'l',
			pkSuffixes: ['id', 'code'],
			skipTables: [
				// Add common tables to skip if needed
				'migrations',
				'sequelize_meta',
			],
			views: true,
			types: {
				citext: 'string', // Map PostgreSQL CITEXT to TypeScript string
			},
			typescript: {
				useDeclare: true, // Use 'declare' keyword instead of '!' for class fields
				strictNullChecks: true, // Use | null for nullable fields instead of ?
			},
		};

		const { tables } = await new SequelizeAuto(sequelize, '', '', options).run();

		// Filter out partition tables (matching _YYYY_MM pattern)
		const filteredTables = {};
		const partitionTableNames = new Set();
		const partitionTableSnakeNames = new Set();
		for (const [tableName, tableData] of Object.entries(tables)) {
			const cleanTableName = tableName.split('.').pop();
			// Skip partition tables matching _YYYY_MM pattern (e.g., auth_audit_logs_2026_05)
			if (!/_\d{4}_\d{2}$/.test(cleanTableName)) {
				filteredTables[tableName] = tableData;
			} else {
				// Track partition table names for post-processing (PascalCase)
				const modelName = this.#toPascalCase(cleanTableName);
				partitionTableNames.add(modelName);
				// Also track snake_case version for property names
				partitionTableSnakeNames.add(cleanTableName);
			}
		}

		console.log(
			`📊 Generated ${Object.keys(filteredTables).length} model files (filtered ${Object.keys(tables).length - Object.keys(filteredTables).length} partition tables)`,
		);

		// Delete partition table model files (they were generated by SequelizeAuto before filtering)
		await this.#deletePartitionTableModels(tables, filteredTables);

		// Post-process generated models to use 'declare' instead of '!' for class fields
		await this.#postProcessModels(partitionTableNames, partitionTableSnakeNames);

		// Generate providers for each table
		await this.#genProviders(filteredTables, db.name, forceRegen);

		// Generate export file
		await this.#genExport(db.name, tables, filteredTables);
	}

	/** @param {object} tables @param {string} dbName @param {boolean} forceRegen */
	async #genProviders(tables, dbName, forceRegen = false) {
		// Ensure providers directory exists
		mkdirSync(this.#providerPath, { recursive: true });

		const providerTemplate = `import { BaseProvider } from "#templates/base/provider";
import { {{ modelName }} } from "#models/{{ modelName }}";

export class {{ providerName }} extends BaseProvider<{{ modelName }}> {
	public static instance: {{ providerName }};

	public static getInstance(): {{ providerName }} {
		{{ providerName }}.instance ??= new {{ providerName }}();
		return {{ providerName }}.instance;
	}

	public static get model() {
		return {{ modelName }};
	}

	constructor() {
		super("{{ tableName }}");
	}
}`;

		await Promise.all(
			Object.keys(tables)
				.filter((tableName) => tableName !== 'default')
				.map(async (tableName) => {
					// Extract table name without schema prefix
					const cleanTableName = tableName.split('.').pop();
					const modelName = this.#toPascalCase(cleanTableName);
					const providerName = `${modelName}Provider`;
					const tableNameLower = cleanTableName.toLowerCase();
					const providerFilePath = resolve(this.#providerPath, `${providerName}.ts`);

					// Skip if provider already exists and not force regenerating
					if (existsSync(providerFilePath) && !forceRegen) {
						console.log(`⏭️  Skipping existing provider: ${providerName}`);
						return;
					}

					if (forceRegen && existsSync(providerFilePath)) {
						console.log(`🔄 Regenerating provider: ${providerName}`);
					} else {
						console.log(`🆕 Generating provider: ${providerName}`);
					}

					const providerContent = mustache.render(providerTemplate, {
						modelName,
						providerName,
						tableName: modelName,
					});

					await writeFile(providerFilePath, providerContent);
				}),
		);

		// Generate Swagger schemas
		await this.#genSwaggerSchemas(tables);
	}

	/** Generate Swagger schemas for all models */
	async #genSwaggerSchemas(tables) {
		console.log('📋 Generating Swagger schemas...');

		const schemas = {};

		for (const [tableName, table] of Object.entries(tables)) {
			if (tableName === 'default') continue;

			const cleanTableName = tableName.split('.').pop();
			const modelName = this.#toPascalCase(cleanTableName);

			// Generate schema for the model
			const properties = {};
			for (const [fieldName, fieldInfo] of Object.entries(table)) {
				properties[fieldName] = this.#getSwaggerType(fieldInfo.type);
			}

			schemas[modelName] = {
				type: 'object',
				properties,
			};

			// Generate mutate schema (without read-only fields)
			const mutateProperties = {};
			for (const [fieldName, fieldInfo] of Object.entries(table)) {
				if (
					!['id', 'created_at', 'created_by', 'updated_at', 'updated_by', 'deleted_at', 'deleted_by'].includes(
						fieldName,
					)
				) {
					mutateProperties[fieldName] = this.#getSwaggerType(fieldInfo.type);
				}
			}

			schemas[`${modelName}Mutate`] = {
				type: 'object',
				properties: mutateProperties,
			};
		}

		// Write schemas to file (disabled - using Zod contracts instead)
		// mkdirSync(this.#swaggerSchemaPath, { recursive: true });
		// await writeFile(
		// 	resolve(this.#swaggerSchemaPath, 'auto-generated-schemas.json'),
		// 	JSON.stringify(this.#sortObject(schemas), null, 2),
		// );

		console.log(`✅ Generated ${Object.keys(schemas).length} Swagger schemas (Zod contracts used for validation)`);
	}

	/** Post-process generated models to use 'declare' keyword and strict null checks */
	async #postProcessModels(partitionTableNames = new Set(), partitionTableSnakeNames = new Set()) {
		console.log("🔧 Post-processing models to use 'declare' keyword, strict null checks, and soft delete scopes...");

		const modelFiles = readdirSync(this.#modelPath).filter((f) => f.endsWith('.ts') && f !== 'init-models.ts');

		for (const fileName of modelFiles) {
			const filePath = resolve(this.#modelPath, fileName);
			let content = await readFile(filePath, 'utf-8');

			// Remove import statements for partition table types
			for (const partitionTableName of partitionTableNames) {
				// Remove import type statements
				content = content.replace(
					new RegExp(
						`import type\\s*\\{[^}]*${partitionTableName}[^}]*\\}\\s*from\\s*'\\./${partitionTableName}';`,
						'g',
					),
					'',
				);
				// Remove regular import statements
				content = content.replace(
					new RegExp(`import\\s*\\{[^}]*${partitionTableName}[^}]*\\}\\s*from\\s*'\\./${partitionTableName}';`, 'g'),
					'',
				);

				// Remove association declarations and mixin methods for partition tables
				// Remove all lines containing the partition table name (PascalCase)
				const linePattern = new RegExp(`^.*${partitionTableName}.*$`, 'gm');
				content = content.replace(linePattern, '');
			}

			// Also remove lines containing snake_case partition table names
			for (const snakeName of partitionTableSnakeNames) {
				// Remove lines containing the snake_case table name in any context
				// Match patterns like: _2026_05, _2026_06, etc.
				const snakePattern = new RegExp(`^.*\\d{4}_\\d{2}.*$`, 'gm');
				content = content.replace(snakePattern, '');
			}

			// Clean up empty lines left by removed imports and associations
			// Replace multiple consecutive empty lines with a single empty line
			content = content.replace(/\n\s*\n\s*\n/g, '\n\n');
			// Also remove single empty lines that are just whitespace
			content = content.replace(/^\s*[\r\n]/gm, '');

			// Replace class field declarations with 'declare' keyword
			// Match patterns like: id!: string; and replace with: declare id: string;
			content = content.replace(/^(\s+)([a-zA-Z_][a-zA-Z0-9_]*)(!)(:\s*[^;]+;)$/gm, '$1declare $2$4');

			// Replace 'any' with 'string' for CITEXT fields (PostgreSQL case-insensitive text)
			// CITEXT is essentially a string type, so we map it to string
			content = content.replace(/:\s*any\b/g, ': string');
			content = content.replace(/:\s*any\s*\|/g, ': string |');

			// Remove unused CreationAttributes imports
			// These are generated but often not used in the model file
			content = content.replace(/,\s*\w+CreationAttributes/g, '');

			// Also ensure all class fields in the model class use 'declare'
			// Match patterns like: fieldName?: type; and replace with: declare fieldName?: type | null;
			// But only within the class body, not in interfaces
			const classMatch = content.match(
				/export class \w+ extends Model<[\s\S]*?>\s*implements [\w<>,\s]*\{\s*([\s\S]*?)\s*\}/,
			);
			if (classMatch) {
				const classBody = classMatch[1];
				// Replace optional declare fields like "declare field?: Type;" with "declare field?: Type | null;"
				let updatedClassBody = classBody.replace(/declare\s+(\w+)\?\s*:\s*([^;]+);/g, 'declare $1?: $2 | null;');
				// Also replace optional fields without declare like "field?: Type;" with "declare field?: Type | null;"
				updatedClassBody = updatedClassBody.replace(/^(\s+)(\w+)\?\s*:\s*([^;]+);/gm, '$1declare $2?: $3 | null;');
				content = content.replace(classBody, updatedClassBody);
			}

			// Also update the interface attributes to use | null for optional fields
			const interfaceMatch = content.match(/export interface \w+Attributes \{([\s\S]*?)\}/);
			if (interfaceMatch) {
				const interfaceBody = interfaceMatch[1];
				// Replace optional fields like "field?: Type;" with "field?: Type | null;"
				let updatedInterfaceBody = interfaceMatch[1].replace(/(\w+)\?\s*:\s*([^;]+);/g, '$1?: $2 | null;');
				content = content.replace(interfaceMatch[1], updatedInterfaceBody);
			}

			// Add soft delete scopes to models that have deleted_at field
			// Only add to regular models (not database views) by checking for timestamps: false, indexes: pattern
			if (content.includes('deleted_at') && content.includes('deleted_by')) {
				// Find the model init options object and add scopes before indexes
				// Match pattern: timestamps: false, indexes: [...]
				const timestampsMatch = content.match(/(timestamps: false,)([\s\S]*?)(indexes: \[)/);

				if (timestampsMatch && !content.includes('scopes:')) {
					// Add Op import if not present (only if we're actually adding scopes)
					if (!content.includes("import { Op } from 'sequelize'")) {
						content = content.replace(
							/^import \* as Sequelize from 'sequelize';/m,
							"import * as Sequelize from 'sequelize';\nimport { Op } from 'sequelize';",
						);
					}

					const scopesConfig = `\n\t\t\tscopes: {\n\t\t\t\tdefault: {\n\t\t\t\t\twhere: {\n\t\t\t\t\t\tdeleted_at: null,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\twithDeleted: {},\n\t\t\t\tonlyDeleted: {\n\t\t\t\t\twhere: {\n\t\t\t\t\t\tdeleted_at: { [Op.ne]: null },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},`;

					// Replace the entire match with timestamps + middle content + scopes + indexes
					const replacement = timestampsMatch[1] + timestampsMatch[2] + scopesConfig + '\n\t\t\tindexes: [';
					content = content.replace(timestampsMatch[0], replacement);
					console.log(`  ✓ Added soft delete scopes to ${fileName}`);
				}
			}

			await writeFile(filePath, content);
		}

		console.log(`✅ Post-processed ${modelFiles.length} model files`);
	}

	/** @param {object} tables @param {object} filteredTables */
	async #deletePartitionTableModels(tables, filteredTables) {
		const partitionTableNames = [];
		for (const tableName of Object.keys(tables)) {
			if (!filteredTables[tableName]) {
				const cleanTableName = tableName.split('.').pop();
				const modelName = this.#toPascalCase(cleanTableName);
				partitionTableNames.push(modelName);
			}
		}

		if (partitionTableNames.length === 0) return;

		console.log(`🗑️  Deleting ${partitionTableNames.length} partition table model files...`);

		for (const modelName of partitionTableNames) {
			const modelFilePath = resolve(this.#modelPath, `${modelName}.ts`);
			if (existsSync(modelFilePath)) {
				await rimraf(modelFilePath);
				console.log(`  ✓ Deleted: ${modelName}.ts`);
			}
		}

		console.log(`✅ Deleted ${partitionTableNames.length} partition table model files`);
	}

	/** @param {string} dbName @param {object} tables @param {object} filteredTables */
	async #genExport(dbName, tables, filteredTables) {
		console.log('📝 Post-processing init-models.ts...');

		const exportFilePath = resolve(this.#modelPath, 'init-models.ts');
		let content = await readFile(exportFilePath, 'utf-8');
		content = content.replace(/\r\n/g, '\n');

		// Identify partition table model names to remove
		const partitionModelNames = [];
		for (const tableName of Object.keys(tables)) {
			if (!filteredTables[tableName]) {
				const cleanTableName = tableName.split('.').pop();
				partitionModelNames.push(this.#toPascalCase(cleanTableName));
			}
		}

		if (partitionModelNames.length === 0) {
			console.log('✅ init-models.ts is up to date');
			return;
		}

		for (const modelName of partitionModelNames) {
			// Remove model import: import { Model as _Model } from './Model';
			content = content.replace(
				new RegExp(
					`import\\s*\\{\\s*${modelName}\\s+as\\s+_${modelName}\\s*\\}\\s*from\\s*['"]\\.\\/${modelName}['"];\\s*\\n`,
					'g',
				),
				'',
			);

			// Remove type imports (single line only)
			content = content.replace(
				new RegExp(
					`import\\s+type\\s*\\{\\s*${modelName}(?:Attributes|CreationAttributes)(?:,\\s*${modelName}(?:Attributes|CreationAttributes))?\\s*\\}\\s*from\\s*['"]\\.\\/${modelName}['"];\\s*\\n`,
					'g',
				),
				'',
			);

			// Remove type imports (multiline block)
			content = content.replace(
				new RegExp(
					`import\\s+type\\s*\\{\\n[ \\t]*${modelName}(?:Attributes|CreationAttributes),?\\n[ \\t]*${modelName}(?:Attributes|CreationAttributes),?\\n[ \\t]*\\}\\s*from\\s*['"]\\.\\/${modelName}['"];\\s*\\n`,
					'g',
				),
				'',
			);

			// Remove from export { ... } alias block
			content = content.replace(new RegExp(`[ \\t]+_${modelName}\\s+as\\s+${modelName},?\\s*\\n`, 'g'), '');

			// Remove type exports
			content = content.replace(new RegExp(`[ \\t]+${modelName}Attributes,?\\s*\\n`, 'g'), '');
			content = content.replace(new RegExp(`[ \\t]+${modelName}CreationAttributes,?\\s*\\n`, 'g'), '');

			// Remove initModel line
			content = content.replace(
				new RegExp(`[ \\t]+const\\s+${modelName}\\s*=\\s+_${modelName}\\.initModel\\(sequelize\\);\\s*\\n`, 'g'),
				'',
			);

			// Remove return entry
			content = content.replace(new RegExp(`[ \\t]+${modelName}:\\s+${modelName},?\\s*\\n`, 'g'), '');
		}

		// Remove association lines referencing partition tables
		const lines = content.split('\n');
		const filteredLines = [];
		for (const line of lines) {
			let skip = false;
			for (const modelName of partitionModelNames) {
				if (
					line.match(new RegExp(`^[ \\t]+${modelName}\\.(?:belongsTo|hasMany|hasOne)\\(`)) ||
					line.match(new RegExp(`\\.(?:belongsTo|hasMany|hasOne)\\(${modelName},`))
				) {
					skip = true;
					break;
				}
			}
			if (!skip) {
				filteredLines.push(line);
			}
		}

		content = filteredLines.join('\n');

		// Clean up multiple empty lines
		content = content.replace(/\n{3,}/g, '\n\n');

		// Fix trailing commas before closing braces
		content = content.replace(/,(\s*\n\s*\})/g, '$1');

		await writeFile(exportFilePath, content);
		console.log(`✅ Post-processed init-models.ts (removed ${partitionModelNames.length} partition table references)`);
	}

	/** @param {string} str */
	#singularize(str) {
		// Simple singularization rules
		if (str.endsWith('ies')) return str.slice(0, -3) + 'y';
		if (str.endsWith('ses')) return str.slice(0, -2);
		if (str.endsWith('s')) return str.slice(0, -1);
		return str;
	}

	/** @param {string} str */
	#toPascalCase(str) {
		const singular = this.#singularize(str);
		return singular
			.split('_')
			.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
			.join('');
	}

	/** @param {boolean} forceRegen */
	async #genProvidersFromTemplate(forceRegen) {
		console.log('Generating providers from template...');

		mkdirSync(this.#providerPath, { recursive: true });

		if (forceRegen && existsSync(this.#providerPath)) {
			console.log('Cleaning existing providers...');
			rimraf.sync(this.#providerPath);
			mkdirSync(this.#providerPath, { recursive: true });
		}

		const initExport = require('../../../src/services/database/sequelize/initExport');
		const modelsObject = initExport.default || initExport;
		const allKeys = Object.keys(modelsObject);
		const modelNames = allKeys.filter((name) => name !== 'sequelize' && name !== 'Sequelize' && name !== 'default');

		console.log('initExport keys:', Object.keys(initExport));
		console.log('modelsObject keys:', allKeys);
		console.log('Filtered model names:', modelNames);

		console.log(`Found ${modelNames.length} models to generate providers for`);

		const providerTemplate = await readFile(`${__dirname}/../api/templates/provider.mustache`, 'utf-8');

		for (const modelName of modelNames) {
			const modelInstance = modelsObject[modelName];
			const modelAttributes = modelInstance?.getAttributes ? Object.keys(modelInstance.getAttributes()) : [];

			const hasStatus = modelAttributes.includes('status');
			const hasIsActive = modelAttributes.includes('isActive') || modelAttributes.includes('is_active');
			const hasIsDeleted =
				modelAttributes.includes('isDeleted') ||
				modelAttributes.includes('is_deleted') ||
				modelAttributes.includes('deleted_at');

			const replace = {
				modelUpperCase: modelName,
				modelLowerCase: modelName.toLowerCase(),
				hasStatus,
				hasIsActive,
				hasIsDeleted,
			};

			const providerFilePath = resolve(this.#providerPath, `${modelName}Provider.ts`);

			const providerContent = mustache.render(providerTemplate, replace);
			await writeFile(providerFilePath, providerContent);

			console.log(`✓ Generated provider: ${modelName}Provider.ts`);
		}

		console.log(`✅ Generated ${modelNames.length} providers`);
	}

	/** Get Swagger type from Sequelize type */
	#getSwaggerType(type) {
		const typeString = type.toString().toLowerCase();
		if (typeString.includes('boolean')) {
			return { type: 'boolean' };
		} else if (
			typeString.includes('int') ||
			typeString.includes('float') ||
			typeString.includes('double') ||
			typeString.includes('decimal')
		) {
			return { type: 'number' };
		} else if (typeString.includes('date') || typeString.includes('time')) {
			return { type: 'string', format: 'date-time' };
		} else if (typeString.includes('json')) {
			return { type: 'object' };
		} else {
			return { type: 'string' };
		}
	}

	/** Sort object keys alphabetically */
	#sortObject(obj) {
		const sortedKeys = Object.keys(obj).sort();
		const sortedObj = {};
		for (const key of sortedKeys) {
			sortedObj[key] = obj[key];
		}
		return sortedObj;
	}
}

new DatabaseGenerator().executor().catch((err) => {
	console.error('Database generation failed:', err);
	process.exit(1);
});
