"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var SolidTsMorphService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SolidTsMorphService = void 0;
const common_1 = require("@nestjs/common");
const node_path_1 = require("node:path");
const node_fs_1 = require("node:fs");
const ts_morph_1 = require("ts-morph");
let SolidTsMorphService = SolidTsMorphService_1 = class SolidTsMorphService {
    constructor() {
        this.logger = new common_1.Logger(SolidTsMorphService_1.name);
        this.inTxn = false;
        this.stagedWrites = new Map();
        this.dirtySourceFiles = new Set();
        this.repoRoot = this.discoverRepoRoot();
        this.workspaceMap = {
            "solid-api": (0, node_path_1.join)(this.repoRoot, "solid-api"),
            "solid-ui": (0, node_path_1.join)(this.repoRoot, "solid-ui"),
        };
        this.project = new ts_morph_1.Project({
            skipAddingFilesFromTsConfig: true,
            manipulationSettings: {
                quoteKind: ts_morph_1.QuoteKind.Double,
                indentationText: ts_morph_1.IndentationText.FourSpaces,
                insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
            },
        });
        this.logger.debug(`SolidTsMorphService repoRoot = ${this.repoRoot}`);
        Object.entries(this.workspaceMap).forEach(([k, v]) => this.logger.debug(`workspace '${k}' => ${v}`));
    }
    discoverRepoRoot() {
        const envRoot = process.env.SOLID_REPO_ROOT;
        if (envRoot && (0, node_path_1.isAbsolute)(envRoot))
            return (0, node_path_1.normalize)(envRoot);
        const cwd = (0, node_path_1.normalize)(process.cwd());
        const base = (0, node_path_1.basename)(cwd);
        if (base === "solid-api" || base === "solid-ui") {
            return (0, node_path_1.normalize)((0, node_path_1.dirname)(cwd));
        }
        return cwd;
    }
    resolveRepoPath(relPath) {
        if (!relPath)
            throw new Error("resolveRepoPath: empty path");
        const p = (0, node_path_1.normalize)(relPath);
        if ((0, node_path_1.isAbsolute)(p))
            return p;
        for (const [prefix, root] of Object.entries(this.workspaceMap)) {
            if (p === prefix || p.startsWith(prefix + "/") || p.startsWith(prefix + "\\")) {
                const suffix = p.slice(prefix.length + (p.length > prefix.length ? 1 : 0));
                return (0, node_path_1.normalize)((0, node_path_1.join)(root, suffix));
            }
        }
        return (0, node_path_1.normalize)((0, node_path_1.join)(this.repoRoot, p));
    }
    rel(abs) {
        const root = this.repoRoot.replace(/\\/g, "/");
        return abs.replace(/\\/g, "/").replace(root + "/", "");
    }
    begin() {
        if (this.inTxn) {
            this.logger.warn("begin(): already in a transaction; reusing current transaction.");
            return;
        }
        this.inTxn = true;
        this.stagedWrites.clear();
        this.dirtySourceFiles.clear();
        this.project = new ts_morph_1.Project({
            skipAddingFilesFromTsConfig: true,
            manipulationSettings: {
                quoteKind: ts_morph_1.QuoteKind.Double,
                indentationText: ts_morph_1.IndentationText.FourSpaces,
                insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
            },
        });
        this.logger.log("Transaction started.");
    }
    rollback() {
        if (!this.inTxn)
            return;
        this.inTxn = false;
        this.stagedWrites.clear();
        this.dirtySourceFiles.clear();
        this.project = new ts_morph_1.Project({
            skipAddingFilesFromTsConfig: true,
            manipulationSettings: {
                quoteKind: ts_morph_1.QuoteKind.Double,
                indentationText: ts_morph_1.IndentationText.FourSpaces,
                insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
            },
        });
        this.logger.log("Transaction rolled back.");
    }
    async commit() {
        if (!this.inTxn) {
            this.logger.log("commit(): not in a transaction; nothing to commit.");
            return { wrote: 0 };
        }
        let writes = 0;
        for (const [abs, { content, overwrite }] of this.stagedWrites.entries()) {
            const dir = (0, node_path_1.dirname)(abs);
            if (!(0, node_fs_1.existsSync)(dir))
                (0, node_fs_1.mkdirSync)(dir, { recursive: true });
            if ((0, node_fs_1.existsSync)(abs) && !overwrite) {
                this.logger.log(`createNewFile (staged): skipped (exists) ${this.rel(abs)}`);
            }
            else {
                (0, node_fs_1.writeFileSync)(abs, content, "utf8");
                writes++;
                this.logger.log(`${(0, node_fs_1.existsSync)(abs) && overwrite ? "Overwrote" : "Created"} file: ${this.rel(abs)}`);
            }
        }
        for (const abs of this.dirtySourceFiles.values()) {
            const sf = this.project.getSourceFile(abs);
            if (!sf)
                continue;
            sf.fixMissingImports();
            sf.organizeImports();
            await sf.save();
            writes++;
            this.logger.log(`Updated module: ${this.rel(abs)}`);
        }
        this.inTxn = false;
        this.stagedWrites.clear();
        this.dirtySourceFiles.clear();
        this.project = new ts_morph_1.Project({
            skipAddingFilesFromTsConfig: true,
            manipulationSettings: {
                quoteKind: ts_morph_1.QuoteKind.Double,
                indentationText: ts_morph_1.IndentationText.FourSpaces,
                insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
            },
        });
        return { wrote: writes };
    }
    createNewFile(path, content, overwrite = false) {
        const abs = this.resolveRepoPath(path);
        const dir = (0, node_path_1.dirname)(abs);
        if (this.inTxn) {
            if (!(0, node_fs_1.existsSync)(dir))
                (0, node_fs_1.mkdirSync)(dir, { recursive: true });
            this.stagedWrites.set(abs, { content, overwrite });
            this.logger.log(`Staged createNewFile: ${this.rel(abs)} (overwrite=${overwrite})`);
            return { createdOrStaged: true, skipped: false };
        }
        if (!(0, node_fs_1.existsSync)(dir))
            (0, node_fs_1.mkdirSync)(dir, { recursive: true });
        if ((0, node_fs_1.existsSync)(abs) && !overwrite) {
            this.logger.log(`createNewFile: skipped (exists): ${path}`);
            return { createdOrStaged: false, skipped: true };
        }
        (0, node_fs_1.writeFileSync)(abs, content, "utf8");
        this.logger.log(`${(0, node_fs_1.existsSync)(abs) && overwrite ? "Overwrote" : "Created"} file: ${path}`);
        return { createdOrStaged: true, skipped: false };
    }
    registerNestProvider(modulePath, providerClassName, importFrom, registerIn, uniqueGuard = true) {
        const abs = this.resolveRepoPath(modulePath);
        if (!(0, node_fs_1.existsSync)(abs))
            throw new Error(`registerNestProvider: module file not found at ${modulePath}`);
        const existing = this.project.getSourceFile(abs);
        const sourceFile = existing
            ? existing
            : this.project.createSourceFile(abs, (0, node_fs_1.readFileSync)(abs, "utf8"), { overwrite: true });
        const imp = sourceFile.getImportDeclarations().find(d => d.getModuleSpecifierValue() === importFrom);
        if (imp) {
            const has = imp.getNamedImports().some(ni => ni.getName() === providerClassName);
            if (!has)
                imp.addNamedImport(providerClassName);
        }
        else {
            sourceFile.addImportDeclaration({ moduleSpecifier: importFrom, namedImports: [providerClassName] });
        }
        const nestModuleClass = sourceFile.getClasses().find(cls => cls.getDecorators().some(dec => dec.getName() === "Module"));
        if (!nestModuleClass)
            throw new Error(`registerNestProvider: No @Module() class found in ${modulePath}`);
        const moduleDec = nestModuleClass.getDecorators().find(dec => dec.getName() === "Module");
        const callExpr = moduleDec.getCallExpression();
        const arg0 = callExpr?.getArguments()[0];
        if (!arg0 || !ts_morph_1.Node.isObjectLiteralExpression(arg0)) {
            throw new Error(`registerNestProvider: Malformed @Module() in ${modulePath}`);
        }
        const meta = arg0;
        const ensureInArray = (propName) => {
            let prop = meta.getProperty(propName);
            if (!prop) {
                meta.addPropertyAssignment({ name: propName, initializer: "[]" });
                prop = meta.getProperty(propName);
            }
            let arr;
            if (ts_morph_1.Node.isPropertyAssignment(prop)) {
                const init = prop.getInitializer();
                if (init && ts_morph_1.Node.isArrayLiteralExpression(init))
                    arr = init;
            }
            if (!arr)
                throw new Error(`registerNestProvider: Property ${propName} is not an array in ${modulePath}`);
            const exists = arr.getElements().some(el => el.getText().replace(/\s/g, "") === providerClassName);
            if (!exists || !uniqueGuard)
                arr.addElement(providerClassName);
        };
        for (const bucket of registerIn)
            ensureInArray(bucket);
        this.dirtySourceFiles.add(abs);
        this.logger.log(`Staged provider registration in: ${this.rel(abs)} (${registerIn.join(", ")})`);
        return { staged: true };
    }
    addMethodToExistingClass(filePath, className, methodName, methodCode) {
        const abs = this.resolveRepoPath(filePath);
        if (!(0, node_fs_1.existsSync)(abs))
            throw new Error(`addMethodToExistingClass: File not found at ${filePath}`);
        const existing = this.project.getSourceFile(abs);
        const sourceFile = existing
            ? existing
            : this.project.createSourceFile(abs, (0, node_fs_1.readFileSync)(abs, "utf8"), { overwrite: true });
        const targetClass = sourceFile.getClass(className);
        if (!targetClass)
            throw new Error(`addMethodToExistingClass: Class ${className} not found in ${filePath}`);
        const existingMethod = targetClass.getMethod(methodName);
        if (existingMethod) {
            this.logger.log(`Skipped adding method '${methodName}' (already exists)`);
            return { staged: false, overwritten: false, skipped: true };
        }
        targetClass.addMember(methodCode);
        this.dirtySourceFiles.add(abs);
        this.logger.log(`Staged method '${methodName}' in class ${className} at ${this.rel(abs)}`);
        return { staged: true, overwritten: !!existingMethod, skipped: false };
    }
    registerSolidUiExtension(filePath, lineToAdd) {
        const abs = this.resolveRepoPath(filePath);
        if (!(0, node_fs_1.existsSync)(abs))
            throw new Error(`registerSolidUiExtension: File not found at ${filePath}`);
        const fileContent = (0, node_fs_1.readFileSync)(abs, "utf8");
        if (fileContent.includes(lineToAdd.trim())) {
            this.logger.log(`Skipped adding line (already exists): ${lineToAdd}`);
            return { staged: false, overwritten: false, skipped: true };
        }
        const newContent = fileContent.trimEnd() + "\n" + lineToAdd.trim() + "\n";
        (0, node_fs_1.writeFileSync)(abs, newContent, "utf8");
        this.dirtySourceFiles.add(abs);
        this.logger.log(`Staged new line in ${this.rel(abs)}: ${lineToAdd}`);
        return { staged: true, overwritten: false, skipped: false };
    }
    removeImports(filePath, filter) {
        const abs = this.resolveRepoPath(filePath);
        if (!(0, node_fs_1.existsSync)(abs)) {
            this.logger.warn(`removeImport: file not found at ${filePath}, skipping.`);
            return { removedIdentifiers: new Set(), staged: false, skipped: true };
        }
        const existing = this.project.getSourceFile(abs);
        const sourceFile = existing
            ? existing
            : this.project.createSourceFile(abs, (0, node_fs_1.readFileSync)(abs, "utf8"), { overwrite: true });
        const importsToRemove = sourceFile.getImportDeclarations().filter(decl => {
            const spec = decl.getModuleSpecifierValue().replace(/\\/g, "/");
            return filter(spec);
        });
        if (importsToRemove.length === 0) {
            return { removedIdentifiers: new Set(), staged: false, skipped: true };
        }
        const removedIdentifiers = new Set();
        for (const decl of importsToRemove) {
            for (const named of decl.getNamedImports()) {
                removedIdentifiers.add(named.getAliasNode()?.getText() ?? named.getName());
            }
            const defaultImport = decl.getDefaultImport();
            if (defaultImport)
                removedIdentifiers.add(defaultImport.getText());
            decl.remove();
        }
        this.dirtySourceFiles.add(abs);
        this.logger.log(`Staged removal of ${importsToRemove.length} import(s) in: ${this.rel(abs)}`);
        return { removedIdentifiers, staged: true, skipped: false };
    }
    removeModuleMembers(filePath, names) {
        const abs = this.resolveRepoPath(filePath);
        if (!(0, node_fs_1.existsSync)(abs)) {
            this.logger.warn(`removeImportMembers: file not found at ${filePath}, skipping.`);
            return { staged: false, skipped: true };
        }
        const identifiers = names instanceof Set ? names : new Set(names);
        if (identifiers.size === 0)
            return { staged: false, skipped: true };
        const existing = this.project.getSourceFile(abs);
        const sourceFile = existing
            ? existing
            : this.project.createSourceFile(abs, (0, node_fs_1.readFileSync)(abs, "utf8"), { overwrite: true });
        const nestModuleClass = sourceFile.getClasses().find(cls => cls.getDecorators().some(dec => dec.getName() === "Module"));
        if (!nestModuleClass) {
            this.logger.warn(`removeImportMembers: no @Module() class found in ${filePath}`);
            return { staged: false, skipped: true };
        }
        const moduleDec = nestModuleClass.getDecorators().find(dec => dec.getName() === "Module");
        const arg0 = moduleDec?.getCallExpression()?.getArguments()[0];
        if (!arg0 || !ts_morph_1.Node.isObjectLiteralExpression(arg0))
            return { staged: false, skipped: true };
        const meta = arg0;
        for (const propName of ["imports", "providers", "controllers", "exports"]) {
            const prop = meta.getProperty(propName);
            if (!prop || !ts_morph_1.Node.isPropertyAssignment(prop))
                continue;
            const init = prop.getInitializer();
            if (!init || !ts_morph_1.Node.isArrayLiteralExpression(init))
                continue;
            const arr = init;
            const elements = arr.getElements();
            for (let i = elements.length - 1; i >= 0; i--) {
                const elemText = elements[i].getText().trim();
                const shouldRemove = identifiers.has(elemText) ||
                    [...identifiers].some(id => new RegExp(`\\b${id}\\b`).test(elemText));
                if (shouldRemove) {
                    arr.removeElement(i);
                }
            }
        }
        this.dirtySourceFiles.add(abs);
        this.logger.log(`Staged removal of [${[...identifiers].join(", ")}] from @Module arrays in: ${this.rel(abs)}`);
        return { staged: true, skipped: false };
    }
    addImport(filePath, importLine) {
        const abs = this.resolveRepoPath(filePath);
        if (!(0, node_fs_1.existsSync)(abs))
            throw new Error(`addImport: File not found at ${filePath}`);
        let fileContent = (0, node_fs_1.readFileSync)(abs, "utf8");
        if (fileContent.includes(importLine.trim())) {
            this.logger.log(`Skipped adding import (already exists): ${importLine}`);
            return { staged: false, overwritten: false, skipped: true };
        }
        const importRegex = /^import .+ from .+;$/gm;
        let lastImportMatch;
        let lastImportIndex = -1;
        while ((lastImportMatch = importRegex.exec(fileContent)) !== null) {
            lastImportIndex = lastImportMatch.index + lastImportMatch[0].length;
        }
        let newContent;
        if (lastImportIndex !== -1) {
            newContent =
                fileContent.slice(0, lastImportIndex) +
                    "\n" +
                    importLine.trim() +
                    "\n" +
                    fileContent.slice(lastImportIndex);
        }
        else {
            newContent = importLine.trim() + "\n\n" + fileContent;
        }
        (0, node_fs_1.writeFileSync)(abs, newContent, "utf8");
        this.dirtySourceFiles.add(abs);
        this.logger.log(`Staged import in ${this.rel(abs)}: ${importLine}`);
        return { staged: true, overwritten: false, skipped: false };
    }
};
exports.SolidTsMorphService = SolidTsMorphService;
exports.SolidTsMorphService = SolidTsMorphService = SolidTsMorphService_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [])
], SolidTsMorphService);
//# sourceMappingURL=solid-ts-morph.service.js.map