"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@oclif/core");
const child_process_1 = require("child_process");
const util_1 = require("util");
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const chalk_1 = __importDefault(require("chalk"));
/**
 * TEACHING NOTE: promisify converts callback-based functions to async/await
 * Old way: exec('command', (error, stdout) => {...})
 * New way: await execAsync('command')
 */
const execAsync = (0, util_1.promisify)(child_process_1.exec);
/**
 * Introspect command - reads your MySQL database structure
 * and generates Prisma schema automatically
 */
class Introspect extends core_1.Command {
    static description = 'Introspect database schema using Prisma';
    static examples = [
        '<%= config.bin %> <%= command.id %>',
        '<%= config.bin %> <%= command.id %> --db mysql://user:pass@localhost:3306/mydb',
    ];
    static flags = {
        db: core_1.Flags.string({
            description: 'Database URL (overrides config)',
            required: false,
        }),
    };
    async run() {
        const { flags } = await this.parse(Introspect);
        this.log(chalk_1.default.bold.blue('\n🔍 Introspecting database schema...\n'));
        // 1. Load config to get database URL
        const config = await this.loadConfig();
        const databaseUrl = flags.db || config.database.url;
        // 2. Check if Prisma is installed
        await this.ensurePrismaInstalled();
        // 3. Run Prisma introspection
        await this.runPrismaIntrospection(databaseUrl);
        // 4. Parse schema to show what was found
        const models = await this.parseSchema();
        // 5. Success message
        this.log(chalk_1.default.green('\n✅ Schema introspection complete!'));
        this.log(chalk_1.default.bold(`📊 Found ${models.length} models: `) + chalk_1.default.cyan(models.join(', ')));
        this.log(chalk_1.default.bold('\nNext step:'));
        this.log('  Run: ' + chalk_1.default.cyan('databridge generate') + '\n');
    }
    /**
     * TEACHING NOTE: Load .databridge.json config file
     * This was created by the "init" command
     */
    async loadConfig() {
        const configPath = path.join(process.cwd(), '.databridge.json');
        if (!(await fs.pathExists(configPath))) {
            this.error(chalk_1.default.red('No .databridge.json found. Run "databridge init" first.'));
        }
        return fs.readJSON(configPath);
    }
    /**
     * TEACHING NOTE: Check if Prisma CLI is installed
     * Prisma is the tool that does the actual database introspection
     */
    async ensurePrismaInstalled() {
        // First check if Prisma exists locally (from bundled deps)
        const localPrisma = path.join(process.cwd(), 'node_modules', '.bin', 'prisma');
        if (await fs.pathExists(localPrisma)) {
            this.log(chalk_1.default.green('✓ Using Prisma from bundled dependencies\n'));
            return;
        }
        // If no local Prisma, check if package.json exists
        const packageJsonPath = path.join(process.cwd(), 'package.json');
        if (!(await fs.pathExists(packageJsonPath))) {
            this.error(chalk_1.default.red('\n❌ No package.json found!') +
                '\n' +
                chalk_1.default.yellow('Did you run "databridge init" first?') +
                '\n' +
                chalk_1.default.gray('The init command creates package.json and copies bundled dependencies.\n'));
        }
        try {
            // Check if prisma is installed and get version
            const { stdout } = await execAsync('npx prisma --version');
            // Check if it's the correct version (5.x for compatibility)
            if (stdout.includes('prisma              : 7.') || stdout.includes('prisma              : 6.')) {
                this.log(chalk_1.default.yellow('⚠️  Incompatible Prisma version detected. Installing Prisma 5.x...'));
                throw new Error('Need to install compatible version');
            }
            this.log(chalk_1.default.green('✓ Using existing Prisma 5.x\n'));
        }
        catch (error) {
            this.log(chalk_1.default.yellow('📦 Installing Prisma 5.x for compatibility...'));
            // Install specific Prisma version that matches our bundle
            this.log('Running: npm install -D prisma@5.22.0 @prisma/client@5.22.0');
            await execAsync('npm install -D prisma@5.22.0 @prisma/client@5.22.0');
            this.log(chalk_1.default.green('✓ Prisma 5.22.0 installed\n'));
        }
    } /**
     * TEACHING NOTE: This is the core magic!
     * "prisma db pull" connects to your database and:
     * 1. Reads all tables and columns
     * 2. Generates Prisma model definitions
     * 3. Updates prisma/schema.prisma file
     *
     * It's like taking a photo of your database structure
     */
    async runPrismaIntrospection(databaseUrl) {
        try {
            // Set DATABASE_URL as environment variable
            // Prisma reads this to know which database to connect to
            const env = {
                ...process.env,
                DATABASE_URL: databaseUrl,
            };
            this.log('Running: ' + chalk_1.default.cyan('prisma db pull'));
            this.log(chalk_1.default.gray('(This may take a few seconds...)\n'));
            // Use local Prisma from node_modules if available, otherwise use npx
            const localPrisma = path.join(process.cwd(), 'node_modules', '.bin', 'prisma');
            const prismaCmd = (await fs.pathExists(localPrisma))
                ? localPrisma
                : 'npx prisma';
            // Execute prisma db pull command
            const { stdout, stderr } = await execAsync(`${prismaCmd} db pull`, {
                env,
                cwd: process.cwd(),
            });
            // Show Prisma output (warnings are in stderr, not always errors)
            if (stderr) {
                this.log(chalk_1.default.yellow(stderr));
            }
            if (stdout) {
                this.log(stdout);
            }
        }
        catch (error) {
            // If Prisma fails, show helpful error message
            this.error(chalk_1.default.red('\n❌ Failed to introspect database:\n') +
                chalk_1.default.gray(error.message) +
                chalk_1.default.yellow('\n\nTips:') +
                '\n  • Check your database URL in .databridge.json' +
                '\n  • Make sure MySQL server is running' +
                '\n  • Verify database credentials are correct\n');
        }
    }
    /**
     * TEACHING NOTE: Parse schema.prisma to extract model names
     * A "model" in Prisma = a table in your database
     *
     * Example schema:
     * model Product {
     *   id   Int    @id
     *   name String
     * }
     *
     * We extract "Product" from this
     */
    async parseSchema() {
        const schemaPath = path.join(process.cwd(), 'prisma', 'schema.prisma');
        if (!(await fs.pathExists(schemaPath))) {
            return [];
        }
        // Read the schema file as text
        const content = await fs.readFile(schemaPath, 'utf-8');
        // Use regex to find all "model ModelName {" patterns
        // \w+ means "one or more word characters" (letters, numbers, underscore)
        const modelRegex = /model\s+(\w+)\s*{/g;
        const models = [];
        let match;
        // Find all matches in the file
        while ((match = modelRegex.exec(content)) !== null) {
            models.push(match[1]); // match[1] is the model name
        }
        return models;
    }
}
exports.default = Introspect;
//# sourceMappingURL=introspect.js.map