/**
 * Copyright (c) Siemens 2016 - 2025
 * SPDX-License-Identifier: MIT
 */
import { normalize } from '@angular-devkit/core';
import { SchematicsException } from '@angular-devkit/schematics';
import { isAbsolute } from 'path/posix';
import ts from 'typescript';
import { SchematicsFileSystem } from './schematics-file-system.js';
/**
 * Reads and parses a tsconfig file from the given path.
 */
export const parseTsconfigFile = (tsconfigPath, basePath, tree) => {
    // Parse the tsconfig file content
    const fs = new SchematicsFileSystem(tree);
    const { config, error } = ts.readConfigFile(tsconfigPath, fs.readText);
    if (error) {
        throw new SchematicsException(`Error parsing ${tsconfigPath}: ${error.messageText}`);
    }
    // Create a ts.ParseConfigHost that uses the schematics Tree
    const parseConfigHost = {
        fileExists: fs.exists,
        readFile: fs.readText,
        readDirectory: (path, _extensions, _excludes, _includes, _depth) => visitDirectory(fs, path),
        useCaseSensitiveFileNames: true
    };
    // Throw if incorrect arguments are passed to this function. Passing relative base paths
    // results in root directories not being resolved and in later type checking runtime errors.
    // More details can be found here: https://github.com/microsoft/TypeScript/issues/37731.
    if (!isAbsolute(basePath)) {
        throw Error('Unexpected relative base path has been specified.');
    }
    return ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, {});
};
/**
 * Finds all nodes which match the given kind.
 */
export const findNodes = (node, kindOrGuard, max = Infinity, recursive = false) => {
    if (!node || max == 0) {
        return [];
    }
    const test = typeof kindOrGuard === 'function'
        ? kindOrGuard
        : // eslint-disable-next-line @typescript-eslint/no-shadow
            (node) => node.kind === kindOrGuard;
    const arr = [];
    if (test(node)) {
        arr.push(node);
        max--;
    }
    if (max > 0 && (recursive || !test(node))) {
        for (const child of node.getChildren()) {
            // eslint-disable-next-line @typescript-eslint/no-loop-func, @typescript-eslint/no-shadow
            findNodes(child, test, max, recursive).forEach(node => {
                if (max > 0) {
                    arr.push(node);
                }
                max--;
            });
            if (max <= 0) {
                break;
            }
        }
    }
    return arr;
};
/**
 * Gets the import nodes from a TypeScript file which start the given import path.
 */
export const getImportNodes = (filePath, content, specifier) => {
    const sourceFile = ts.createSourceFile(filePath, content.toString(), ts.ScriptTarget.Latest, true);
    // Find all import declarations in the source file
    const allImports = findNodes(sourceFile, ts.SyntaxKind.ImportDeclaration);
    return allImports.filter(node => node.moduleSpecifier &&
        ts.isStringLiteral(node.moduleSpecifier) &&
        node.moduleSpecifier.text.startsWith(specifier));
};
/**
 * Gets the imported symbols from an import declaration.
 */
export const getSymbols = (node) => {
    // Extract all imported component names or module names from @simpl/ imports
    return node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)
        ? node.importClause.namedBindings.elements
        : [];
};
const visitDirectory = (fs, dirPath) => {
    const entries = fs.getDir(dirPath);
    const files = [];
    entries.subfiles.forEach(filename => {
        if (!filename.startsWith('.')) {
            const fullPath = normalize(`${dirPath}/${filename}`);
            files.push(fullPath);
        }
    });
    entries.subdirs.forEach(subdirname => {
        if (!subdirname.startsWith('.') && subdirname !== 'node_modules') {
            const newFiles = visitDirectory(fs, `${dirPath}/${subdirname}`);
            files.push(...newFiles);
        }
    });
    return files;
};
/** Finds an import specifier with a particular name. */
export const findImportSpecifier = (nodes, specifierName) => {
    return nodes.find(element => {
        const { name, propertyName } = element;
        return propertyName ? propertyName.text === specifierName : name.text === specifierName;
    });
};
/**
 * Note: returns only matching imports specifiers,
 * Unmatched imports will be ignored (you won't get undefined), but a shorter array.
 */
export const getImportSpecifiers = (sourceFile, moduleName, specifierOrSpecifiers) => {
    const matches = [];
    for (const node of sourceFile.statements) {
        if (!ts.isImportDeclaration(node) || !ts.isStringLiteral(node.moduleSpecifier)) {
            continue;
        }
        const namedBindings = node.importClause?.namedBindings;
        const isMatch = typeof moduleName === 'string'
            ? node.moduleSpecifier.text === moduleName
            : moduleName.test(node.moduleSpecifier.text);
        if (!isMatch || !namedBindings || !ts.isNamedImports(namedBindings)) {
            continue;
        }
        if (typeof specifierOrSpecifiers === 'string') {
            const match = findImportSpecifier(namedBindings.elements, specifierOrSpecifiers);
            if (match) {
                matches.push(match);
            }
        }
        else {
            for (const specifierName of specifierOrSpecifiers) {
                const match = findImportSpecifier(namedBindings.elements, specifierName);
                if (match) {
                    matches.push(match);
                }
            }
        }
    }
    return matches;
};
export function* renameIdentifier({ sourceFile, renamingInstructions }) {
    for (const node of sourceFile.statements) {
        if (!ts.isImportDeclaration(node) || !ts.isStringLiteral(node.moduleSpecifier)) {
            continue;
        }
        for (const renamingInstruction of renamingInstructions) {
            if (!renamingInstruction.module.test(node.moduleSpecifier.text)) {
                continue;
            }
            if (!(node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings))) {
                continue;
            }
            for (const [index, { replace, replaceWith }] of renamingInstruction.symbolRenamings.entries()) {
                const importSpecifiers = findImportSpecifier(node.importClause.namedBindings.elements, replace);
                if (!importSpecifiers) {
                    continue;
                }
                yield {
                    start: importSpecifiers.name.getStart(),
                    width: importSpecifiers.name.getWidth(),
                    newNode: ts.factory.createIdentifier(replaceWith)
                };
                if (renamingInstruction.toModule &&
                    !node.moduleSpecifier.text.endsWith('@simpl/element-ng') &&
                    index === 0) {
                    const newPath = node.moduleSpecifier.text.replace(renamingInstruction.module, renamingInstruction.toModule);
                    yield {
                        start: node.moduleSpecifier.getStart(),
                        width: node.moduleSpecifier.getWidth(),
                        newNode: ts.factory.createStringLiteral(newPath, true)
                    };
                }
                const visitor = function* (visitedNode) {
                    if (ts.isIdentifier(visitedNode) && visitedNode.text === replace) {
                        yield {
                            start: visitedNode.getStart(),
                            width: visitedNode.getWidth(),
                            newNode: ts.factory.createIdentifier(replaceWith)
                        };
                    }
                    else {
                        for (const child of visitedNode.getChildren()) {
                            yield* visitor(child);
                        }
                    }
                };
                for (const statement of sourceFile.statements) {
                    if (!ts.isImportDeclaration(statement)) {
                        yield* visitor(statement);
                    }
                }
            }
        }
    }
}
