"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectFile = void 0;
const sdk_build_core_1 = require("@servicenow/sdk-build-core");
const util_1 = require("./util");
class ProjectFile {
    file;
    project;
    readOnly;
    constructor(file, project, readOnly = false) {
        this.file = file;
        this.project = project;
        this.readOnly = readOnly;
    }
    getPath() {
        return sdk_build_core_1.path.normalize(this.file instanceof sdk_build_core_1.ts.SourceFile ? this.file.getFilePath() : this.file.path);
    }
    getName() {
        return sdk_build_core_1.path.basename(this.getPath());
    }
    getContent() {
        return this.file instanceof sdk_build_core_1.ts.SourceFile ? this.file.getFullText() : this.file.content;
    }
    setContent(content) {
        if (this.file instanceof sdk_build_core_1.ts.SourceFile) {
            this.file.replaceText([this.file.getPos(), this.file.getEnd()], content);
        }
        else {
            this.file.content = content;
            // Content was explicitly set — clear originalPath so save() writes the
            // new content rather than copying the original (which would discard the update).
            delete this.file.originalPath;
        }
        this.reload(false);
    }
    setReadOnly(readOnly) {
        this.readOnly = readOnly;
    }
    async getDiagnostics() {
        await this.getRecords(false);
        return this.project.getDiagnosticsService().getAll(this.getPath());
    }
    async getShapes(throwOnError = true) {
        const shapes = [];
        const { diagnostics, compiler, transform, logger } = this.project.getContext();
        if (this.isKeysRegistry()) {
            return shapes;
        }
        if (this.file instanceof sdk_build_core_1.ts.SourceFile) {
            diagnostics.clear(this.getPath());
            if (shouldResolveDependencies()) {
                compiler.resolveSourceFileDependencies();
            }
            compiler.getDiagnosticsForFluentFile(this.file).forEach((d) => diagnostics.add(d));
            const filePath = this.file.getFilePath();
            await compiler.visitSupportedNodes(this.file, async (node) => {
                try {
                    const result = await transform.toShape(node);
                    if (result.success && filePath === result.value.getOriginalFilePath()) {
                        shapes.push(result.value);
                    }
                }
                catch (e) {
                    logger.error('Shape creation failed:', e);
                }
            }, (node) => {
                if ((0, sdk_build_core_1.isFluentNode)(node)) {
                    diagnostics.error(node, `Syntax kind "${(0, sdk_build_core_1.getUnsupportedKindName)(node)}" is not allowed in Fluent files.`);
                }
            });
        }
        if (throwOnError) {
            const errors = diagnostics.getErrors(this.getPath());
            errors.forEach((e) => logger.error(e.getFormattedText()));
            if (errors.length > 0) {
                throw new Error(`Found ${errors.length} diagnostic error(s) while getting shapes from file: ${this.getPath()}`);
            }
        }
        return shapes;
    }
    async getRecords(throwOnError = true) {
        return new sdk_build_core_1.Database(await this.getRecordsNoDedupe(throwOnError));
    }
    async getRecordsNoDedupe(throwOnError = true, sysIds) {
        const records = [];
        const { diagnostics, compiler, transform, keys, logger, inspect } = this.project.getContext();
        if (this.getPath() === keys.getFilePath()) {
            return records;
        }
        if (this.file instanceof sdk_build_core_1.ts.SourceFile) {
            diagnostics.clear(this.getPath());
            if (shouldResolveDependencies()) {
                compiler.resolveSourceFileDependencies();
            }
            const filePath = this.file.getFilePath();
            await compiler.visitSupportedNodes(this.file, async (node) => {
                const plugins = this.project.getPlugins();
                const context = this.project.getContext();
                const entryPoint = sdk_build_core_1.ts.Node.isSourceFile(node) ? node.getFilePath() : node;
                if (await plugins.isEntryPoint(entryPoint, context)) {
                    try {
                        const result = await transform.toRecord(node);
                        if (result.success && filePath === result.value.getOriginalFilePath()) {
                            const flatResult = result.value.flat();
                            if (!sysIds ||
                                (sysIds && flatResult.find((value) => sysIds?.has(value.getId().getValue())))) {
                                records.push(...flatResult);
                            }
                        }
                    }
                    catch (e) {
                        logger.error('Record creation failed:', e);
                    }
                }
            }, (node) => {
                if ((0, sdk_build_core_1.isFluentNode)(node)) {
                    diagnostics.error(node, `Node kind "${(0, sdk_build_core_1.getUnsupportedKindName)(node)}" is not allowed in Fluent files.`);
                }
            });
            compiler.getDiagnosticsForFluentFile(this.file).forEach((d) => diagnostics.add(d));
        }
        else {
            const filePath = this.file.path;
            try {
                const result = await transform.toRecord(this.file);
                if (result.success) {
                    if (filePath === result.value.getOriginalFilePath() || this.isUpdateSetXml()) {
                        const flatResult = result.value.flat();
                        if (!sysIds || (sysIds && flatResult.find((value) => sysIds?.has(value.getId().getValue())))) {
                            records.push(...flatResult);
                        }
                    }
                }
            }
            catch (e) {
                logger.error('Record creation failed:', e);
            }
        }
        records.forEach((r) => inspect(r));
        if (throwOnError) {
            const errors = diagnostics.getErrors(this.getPath());
            errors.forEach((e) => logger.error(e.getFormattedText()));
            if (errors.length > 0) {
                throw new Error(`Found ${errors.length} diagnostic error(s) while getting shapes from file: ${this.getPath()}`);
            }
        }
        return records;
    }
    async getOutput(throwOnError = true) {
        return (0, util_1.getOutput)(await this.getRecords(throwOnError), this.project.getContext());
    }
    isUpdateSetXml() {
        if (this.file instanceof sdk_build_core_1.ts.SourceFile) {
            return false;
        }
        if (this.file.path.match('sys_remote_update_set') || this.file.path.match('sys_update_set')) {
            return this.file.path.endsWith('.xml');
        }
        return false;
    }
    isInMetadataDir() {
        return this.project.isInMetadataDir(this.getPath());
    }
    isInFluentHostedPluginDir(pluginId) {
        return this.project.isInFluentHostedPluginDir(this.getPath(), pluginId);
    }
    isInServerModulesDir() {
        return this.project.isInServerDir(this.getPath());
    }
    isInUiHtmlContentDir() {
        return this.project.isInUiHtmlContentDir(this.getPath());
    }
    isKeysRegistry() {
        return this.getPath() === this.project.getKeys().getFilePath();
    }
    reload(refreshFromFileSystem = true) {
        if (this.file instanceof sdk_build_core_1.ts.SourceFile) {
            this.project.getDiagnosticsService().clear(this.getPath());
            this.project.getCompiler().removeTableInterfaceFromGlobalDeclaration(this.file);
        }
        this.project.getPlugins().reload();
        if (refreshFromFileSystem) {
            if (this.file instanceof sdk_build_core_1.ts.SourceFile) {
                this.file.refreshFromFileSystemSync();
            }
            else if (sdk_build_core_1.FileSystem.existsSync(this.project.getFileSystem(), this.file.path)) {
                this.file.content = this.project.getFileSystem().readFileSync(this.file.path, { encoding: 'utf-8' });
            }
        }
        if (this.isKeysRegistry()) {
            this.project.getKeys().reload();
        }
    }
    fix() {
        if (!(this.file instanceof sdk_build_core_1.ts.SourceFile)) {
            return;
        }
        this.file.fixMissingImports(undefined, {
            autoImportSpecifierExcludeRegexes: ['^@servicenow/sdk-core(/|$)'],
        });
        //uses the exact same APIs used inside `fixUnusedIdentifiers` API.
        //https://github.com/dsherret/ts-morph/blob/886ad69eca9566c38a23a7dbf6047e89ea8b0d3b/packages/ts-morph/src/compiler/ast/module/SourceFile.ts#L894
        deleteUnusedCode(this.file, this.project);
        //Handle https://github.com/dsherret/ts-morph/issues/961
        const fixedText = fixAdditionalTrailingCommas(this.getContent());
        this.setContent(fixedText);
    }
    // TODO: Why constrain to just ts.SourceFile?
    async format(formatter = new sdk_build_core_1.Formatter(this.project.getFileSystem())) {
        if (this.file instanceof sdk_build_core_1.ts.SourceFile) {
            this.setContent(await formatter.format(this.getContent(), this.getPath()));
        }
    }
    save() {
        if (this.file instanceof sdk_build_core_1.ts.SourceFile) {
            this.file.saveSync();
        }
        else if (!this.readOnly) {
            (0, util_1.writeFileWithParentDirs)(this.project.getFileSystem(), this.file);
        }
    }
    remove() {
        if (this.file instanceof sdk_build_core_1.ts.SourceFile) {
            this.project.getDiagnosticsService().clear(this.getPath());
            const compiler = this.project.getCompiler();
            compiler.removeTableInterfaceFromGlobalDeclaration(this.getPath());
            compiler.removeSourceFile(this.file);
        }
    }
    unwrap() {
        return this.file;
    }
    addStatement(code) {
        if (!(this.file instanceof sdk_build_core_1.ts.SourceFile)) {
            throw new Error(`Cannot add statement to non-TypeScript file: ${this.getPath()}`);
        }
        // TODO: Insert newlines between statements if we're adding to an existing file
        const [statement, otherStatement] = this.file.addStatements(code ?? 'undefined');
        if (!statement) {
            throw new Error('Expected statement to be generated');
        }
        if (otherStatement) {
            throw new Error(`Expected one statement to be generated, but found multiple. It's likely that a syntax error was inserted.\nProvided statement:\n${code}`);
        }
        return statement;
    }
}
exports.ProjectFile = ProjectFile;
function fixAdditionalTrailingCommas(fileText) {
    return fileText.replace(/,{2,}$/gm, ',');
}
const unusedChangesIterator = (file, compiler, callback) => {
    const unusedFixes = compiler.getLanguageService().getCombinedCodeFix(file, 'unusedIdentifier_delete');
    unusedFixes.getChanges().forEach((change) => {
        callback(change);
    });
};
/**
 * Uses language service code fixes to get unused identifiers fix and tries to fix call expressions assigned to unused identifiers.
 * Ex:
 *  before: const x = Entity({})
 *  after: Entity({})
 * @param file TypeScript file
 * @param compiler TypeScript compiler
 */
function fixUnusedCode(file, compiler) {
    if ((0, sdk_build_core_1.hasDirective)(file, '@fluent-disable-sync-for-file')) {
        return;
    }
    unusedChangesIterator(file, compiler, (change) => {
        // pre-compute nodes to avoid getting incorrect nodes when the file is modified (node.replaceWithText below)
        const nodes = change.getTextChanges().map((textChange) => {
            return file.getDescendantAtPos(textChange.getSpan().getStart());
        });
        nodes.forEach((node) => {
            // if node is a comment node, the whole comment and sibling variable statement will be be removed
            // to avoid that we get the variable statement node
            if (node?.isKind(sdk_build_core_1.ts.SyntaxKind.SingleLineCommentTrivia) ||
                node?.isKind(sdk_build_core_1.ts.SyntaxKind.MultiLineCommentTrivia)) {
                node = node?.getNextSiblingIfKind(sdk_build_core_1.ts.SyntaxKind.VariableStatement);
            }
            else {
                node = node?.getFirstAncestorByKind(sdk_build_core_1.ts.SyntaxKind.VariableStatement);
            }
            const variableStatement = node?.asKind(sdk_build_core_1.ts.SyntaxKind.VariableStatement);
            const callExp = variableStatement?.getDeclarations()[0]?.getInitializerIfKind(sdk_build_core_1.ts.SyntaxKind.CallExpression);
            if (callExp && node && !variableStatement?.isExported()) {
                node.replaceWithText(callExp.getText());
            }
        });
    });
}
function deleteUnusedCode(file, project) {
    fixUnusedCode(file, project.getCompiler());
    const unusedCodeArr = [];
    unusedChangesIterator(file, project.getCompiler(), (change) => {
        change.getTextChanges().forEach((textChange) => {
            const textSpan = textChange.getSpan();
            unusedCodeArr.push(file.getFullText().substring(textSpan.getStart(), textSpan.getEnd()));
        });
        change.applyChanges();
    });
    if (unusedCodeArr.length > 0) {
        //logs all the code being removed during debug mode
        project
            .getLogger()
            .debug([
            `Removing the following unused code from ${file.getFilePath().replace(project.getRootDir(), '')}:`,
            ...unusedCodeArr.map((unusedCode, idx) => `${idx + 1}:${unusedCode.trim()}`),
        ].join('\n\n'));
    }
    project.getCompiler().getLanguageService().getCombinedCodeFix(file, 'unusedIdentifier_deleteImports').applyChanges();
}
function shouldResolveDependencies() {
    const envValue = process.env['FLUENT_RESOLVE_DEPENDENCIES'];
    if (envValue === undefined || envValue === '') {
        return true;
    }
    return envValue !== 'false' && envValue !== '0';
}
//# sourceMappingURL=project-file.js.map