"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupNgStarterComponents = setupNgStarterComponents;
const schematics_1 = require("@angular-devkit/schematics");
const tasks_1 = require("@angular-devkit/schematics/tasks");
const packageJson = require('../package.json');
const PACKAGE_JSON_PATH = '/package.json';
const ANGULAR_JSON_PATH = '/angular.json';
const POSTCSS_CONFIG_PATH = '/.postcssrc.json';
const POSTCSS_CONFIGS = [
    '/.postcssrc',
    '/.postcssrc.json',
    '/.postcssrc.yaml',
    '/.postcssrc.yml',
    '/.postcssrc.js',
    '/.postcssrc.cjs',
    '/postcss.config.js',
    '/postcss.config.cjs',
    '/postcss.config.mjs',
];
const DEPENDENCY_SECTIONS = [
    'dependencies',
    'devDependencies',
    'peerDependencies',
    'optionalDependencies',
];
const DEV_DEPENDENCY_NAMES = new Set([
    '@tailwindcss/postcss',
    '@tailwindcss/typography',
    'autoprefixer',
    'postcss',
    'tailwindcss',
]);
const GOOGLE_FONTS_HREF = 'https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&amp;family=Sora:wght@500;600;700&amp;display=swap';
const GOOGLE_FONTS_HREF_PATTERN = /https:\/\/fonts\.googleapis\.com\/css2\?family=Manrope:wght@400;500;600;700;800(?:&amp;|&)family=Sora:wght@500;600;700(?:&amp;|&)display=swap/;
const GOOGLE_FONTS_LINK = `<link href="${GOOGLE_FONTS_HREF}" rel="stylesheet"/>`;
const NGS_THEME_IMPORT = '@use "@ngstarter-ui/components/styles/themes/default";';
const NGS_THEME_PROVIDER = `provideNgsTheme({
  theme: 'default',
  colorScheme: 'auto',
  radius: 'medium',
}),`;
function setupNgStarterComponents(tree, context, options) {
    validateAngularVersion(tree);
    updatePackageJson(tree, options.updateExistingDependencies);
    setupTailwindFiles(tree, context, options.project);
    if (!options.skipInstall) {
        context.addTask(new tasks_1.NodePackageInstallTask());
        context.logger.info('Installing @ngstarter-ui/components dependencies...');
    }
}
function updatePackageJson(tree, updateExistingDependencies) {
    var _a;
    const projectPackageJson = readJson(tree, PACKAGE_JSON_PATH);
    const dependencies = collectPackageDependencies(packageJson.dependencies, packageJson.peerDependencies);
    const runtimeDependencies = filterDependencies(dependencies, false);
    const devDependencies = Object.assign(Object.assign({}, filterDependencies(dependencies, true)), ((_a = packageJson.devDependencies) !== null && _a !== void 0 ? _a : {}));
    addDependencies(projectPackageJson, runtimeDependencies, 'dependencies', updateExistingDependencies);
    addDependencies(projectPackageJson, devDependencies, 'devDependencies', updateExistingDependencies);
    writeJson(tree, PACKAGE_JSON_PATH, projectPackageJson);
}
function collectPackageDependencies(dependencies, peerDependencies) {
    return Object.assign(Object.assign({}, (peerDependencies !== null && peerDependencies !== void 0 ? peerDependencies : {})), (dependencies !== null && dependencies !== void 0 ? dependencies : {}));
}
function filterDependencies(dependencies, includeDevDependencies) {
    return Object.keys(dependencies).reduce((result, name) => {
        if (DEV_DEPENDENCY_NAMES.has(name) === includeDevDependencies) {
            result[name] = dependencies[name];
        }
        return result;
    }, {});
}
function addDependencies(projectPackageJson, dependencies, fallbackSection, updateExistingDependencies) {
    Object.keys(dependencies).sort().forEach(name => {
        if (name === packageJson.name) {
            return;
        }
        const version = dependencies[name];
        const existingSection = findDependencySection(projectPackageJson, name);
        if (existingSection) {
            if (updateExistingDependencies) {
                projectPackageJson[existingSection][name] = version;
                sortDependencySection(projectPackageJson, existingSection);
            }
            return;
        }
        if (!projectPackageJson[fallbackSection]) {
            projectPackageJson[fallbackSection] = {};
        }
        projectPackageJson[fallbackSection][name] = version;
        sortDependencySection(projectPackageJson, fallbackSection);
    });
}
function findDependencySection(projectPackageJson, name) {
    return DEPENDENCY_SECTIONS.find(section => { var _a; return ((_a = projectPackageJson[section]) === null || _a === void 0 ? void 0 : _a[name]) !== undefined; });
}
function sortDependencySection(projectPackageJson, section) {
    const dependencies = projectPackageJson[section];
    if (!dependencies) {
        return;
    }
    projectPackageJson[section] = Object.keys(dependencies)
        .sort()
        .reduce((result, name) => {
        result[name] = dependencies[name];
        return result;
    }, {});
}
function setupTailwindFiles(tree, context, projectName) {
    var _a;
    createPostcssConfig(tree, context);
    if (!tree.exists(ANGULAR_JSON_PATH)) {
        context.logger.warn('No angular.json found. Skipping global styles configuration.');
        return;
    }
    const workspace = readJson(tree, ANGULAR_JSON_PATH);
    const resolvedProjectName = resolveProjectName(workspace, projectName);
    const project = (_a = workspace.projects) === null || _a === void 0 ? void 0 : _a[resolvedProjectName];
    if (!project) {
        throw new schematics_1.SchematicsException(`Project "${resolvedProjectName}" was not found in angular.json.`);
    }
    const buildOptions = getBuildOptions(project);
    const stylePath = resolveGlobalStylePath(project, buildOptions);
    const indexPath = resolveIndexHtmlPath(project, buildOptions);
    const appConfigPath = resolveAppConfigPath(project);
    ensureGlobalStyleFile(tree, stylePath);
    ensureIndexGoogleFontsLink(tree, context, indexPath);
    ensureAppConfigThemeProvider(tree, context, appConfigPath);
    ensureProjectStyleEntry(buildOptions, stylePath);
    writeJson(tree, ANGULAR_JSON_PATH, workspace);
}
function validateAngularVersion(tree) {
    var _a;
    const projectPackageJson = readJson(tree, PACKAGE_JSON_PATH);
    const requiredAngularVersion = (_a = packageJson.peerDependencies) === null || _a === void 0 ? void 0 : _a['@angular/core'];
    if (!requiredAngularVersion) {
        return;
    }
    const projectAngularVersion = findDependencyVersion(projectPackageJson, '@angular/core');
    if (!projectAngularVersion) {
        throw new schematics_1.SchematicsException(`Required dependency "@angular/core" was not found in "${PACKAGE_JSON_PATH}".`);
    }
    const supportedMajors = getMajorVersions(requiredAngularVersion);
    const projectMajor = getMajorVersion(projectAngularVersion);
    if (supportedMajors.length === 0 || projectMajor === undefined) {
        throw new schematics_1.SchematicsException(`Could not verify Angular compatibility. Required: ${requiredAngularVersion}, found: ${projectAngularVersion}.`);
    }
    if (!supportedMajors.includes(projectMajor)) {
        throw new schematics_1.SchematicsException(`${packageJson.name} requires Angular ${requiredAngularVersion}, but the project uses @angular/core ${projectAngularVersion}. ` +
            `Run "ng update @angular/core@${supportedMajors[0]} @angular/cli@${supportedMajors[0]}" before adding ${packageJson.name}.`);
    }
}
function findDependencyVersion(projectPackageJson, name) {
    var _a;
    const section = findDependencySection(projectPackageJson, name);
    const version = section ? (_a = projectPackageJson[section]) === null || _a === void 0 ? void 0 : _a[name] : undefined;
    return typeof version === 'string' ? version : undefined;
}
function getMajorVersions(versionRange) {
    const majors = new Set();
    const matches = versionRange.matchAll(/(\d+)\.\d+\.\d+/g);
    for (const match of matches) {
        majors.add(Number(match[1]));
    }
    return [...majors].sort((a, b) => a - b);
}
function getMajorVersion(versionRange) {
    return getMajorVersions(versionRange)[0];
}
function createPostcssConfig(tree, context) {
    const hasPostcssConfig = POSTCSS_CONFIGS.some(path => tree.exists(path));
    if (hasPostcssConfig) {
        context.logger.info('Existing PostCSS configuration found. Skipping .postcssrc.json creation.');
        return;
    }
    writeJson(tree, POSTCSS_CONFIG_PATH, {
        plugins: {
            '@tailwindcss/postcss': {},
        },
    });
}
function resolveProjectName(workspace, projectName) {
    var _a;
    if (projectName) {
        return projectName;
    }
    if (workspace.defaultProject) {
        return workspace.defaultProject;
    }
    const projects = (_a = workspace.projects) !== null && _a !== void 0 ? _a : {};
    const applicationProject = Object.keys(projects).find(name => projects[name].projectType === 'application');
    if (!applicationProject) {
        throw new schematics_1.SchematicsException('No Angular application project was found in angular.json.');
    }
    return applicationProject;
}
function getBuildOptions(project) {
    var _a, _b, _c;
    const buildTarget = (_b = (_a = project.architect) === null || _a === void 0 ? void 0 : _a['build']) !== null && _b !== void 0 ? _b : (_c = project.targets) === null || _c === void 0 ? void 0 : _c['build'];
    const options = buildTarget === null || buildTarget === void 0 ? void 0 : buildTarget['options'];
    if (!options) {
        throw new schematics_1.SchematicsException('The selected project does not have build options in angular.json.');
    }
    return options;
}
function resolveGlobalStylePath(project, buildOptions) {
    const styles = Array.isArray(buildOptions['styles']) ? buildOptions['styles'] : [];
    const firstStylePath = styles.reduce((result, style) => {
        if (result) {
            return result;
        }
        if (typeof style === 'string') {
            return style;
        }
        if (style && typeof style === 'object' && typeof style['input'] === 'string') {
            return style['input'];
        }
        return undefined;
    }, undefined);
    if (firstStylePath) {
        const stylePath = normalizeWorkspacePath(firstStylePath);
        assertScssStylePath(stylePath);
        return stylePath;
    }
    const sourceRoot = project.sourceRoot || joinWorkspacePath(project.root || '', 'src');
    const stylePath = normalizeWorkspacePath(joinWorkspacePath(sourceRoot, 'styles.scss'));
    assertScssStylePath(stylePath);
    return stylePath;
}
function resolveIndexHtmlPath(project, buildOptions) {
    const index = buildOptions['index'];
    if (typeof index === 'string') {
        return normalizeWorkspacePath(index);
    }
    if (index && typeof index === 'object' && typeof index['input'] === 'string') {
        return normalizeWorkspacePath(index['input']);
    }
    const sourceRoot = project.sourceRoot || joinWorkspacePath(project.root || '', 'src');
    return normalizeWorkspacePath(joinWorkspacePath(sourceRoot, 'index.html'));
}
function resolveAppConfigPath(project) {
    const sourceRoot = project.sourceRoot || joinWorkspacePath(project.root || '', 'src');
    return normalizeWorkspacePath(joinWorkspacePath(sourceRoot, 'app/app.config.ts'));
}
function assertScssStylePath(stylePath) {
    if (stylePath.endsWith('.scss')) {
        return;
    }
    throw new schematics_1.SchematicsException(`${packageJson.name} supports only SCSS global styles. The selected project uses "${stylePath}". ` +
        'Switch the project global style file to styles.scss and retry.');
}
function ensureIndexGoogleFontsLink(tree, context, indexPath) {
    if (!tree.exists(indexPath)) {
        context.logger.warn(`Index file "${indexPath}" was not found. Skipping Google Fonts setup.`);
        return;
    }
    const content = readText(tree, indexPath);
    if (GOOGLE_FONTS_HREF_PATTERN.test(content)) {
        return;
    }
    const headMatch = /([ \t]*)<\/head>/i.exec(content);
    const nextContent = headMatch
        ? content.replace(/([ \t]*)<\/head>/i, (_match, indent, offset) => {
            const needsLeadingNewline = !content.slice(0, offset).endsWith('\n');
            return `${needsLeadingNewline ? '\n' : ''}${indent}  ${GOOGLE_FONTS_LINK}\n${indent}</head>`;
        })
        : `${content.trimEnd()}\n${GOOGLE_FONTS_LINK}\n`;
    tree.overwrite(indexPath, nextContent);
}
function ensureAppConfigThemeProvider(tree, context, appConfigPath) {
    if (!tree.exists(appConfigPath)) {
        context.logger.warn(`App config "${appConfigPath}" was not found. Skipping NgStarter theme provider setup.`);
        return;
    }
    const content = readText(tree, appConfigPath);
    if (/provideNgsTheme\s*\(/.test(content)) {
        return;
    }
    if (!/providers\s*:\s*\[/.test(content)) {
        context.logger.warn(`No providers array found in "${appConfigPath}". Skipping NgStarter theme provider setup.`);
        return;
    }
    const nextContent = insertThemeProvider(addCoreImport(content));
    tree.overwrite(appConfigPath, nextContent);
}
function addCoreImport(content) {
    var _a;
    const coreImport = /import\s*\{([\s\S]*?)\}\s*from\s*['"]@ngstarter-ui\/components\/core['"];?/m;
    const match = coreImport.exec(content);
    if (match) {
        const imports = match[1]
            .split(',')
            .map(item => item.trim())
            .filter(Boolean);
        if (!imports.includes('provideNgsTheme')) {
            imports.push('provideNgsTheme');
        }
        return content.replace(coreImport, `import { ${imports.sort().join(', ')} } from '@ngstarter-ui/components/core';`);
    }
    const importStatement = `import { provideNgsTheme } from '@ngstarter-ui/components/core';`;
    const imports = [...content.matchAll(/import[\s\S]*?from\s*['"][^'"]+['"];?/g)];
    const lastImport = imports[imports.length - 1];
    if (!lastImport) {
        return `${importStatement}\n${content}`;
    }
    const insertIndex = ((_a = lastImport.index) !== null && _a !== void 0 ? _a : 0) + lastImport[0].length;
    return `${content.slice(0, insertIndex)}\n${importStatement}${content.slice(insertIndex)}`;
}
function insertThemeProvider(content) {
    return content.replace(/(\n([ \t]*)providers\s*:\s*\[)([ \t]*)(\r?\n)?/, (_match, start, indent) => {
        const providerIndent = `${indent}  `;
        return `${start}\n${indentBlock(NGS_THEME_PROVIDER, providerIndent)}\n`;
    });
}
function indentBlock(value, indent) {
    return value
        .split('\n')
        .map(line => `${indent}${line}`)
        .join('\n');
}
function ensureGlobalStyleFile(tree, stylePath) {
    const content = tree.exists(stylePath) ? readText(tree, stylePath) : '';
    const imports = [
        {
            needle: /@use\s+["']@ngstarter-ui\/components\/styles\/themes\/[^"']+["'];?/,
            statement: NGS_THEME_IMPORT,
        },
    ];
    const missingImports = imports
        .filter(item => !item.needle.test(content))
        .map(item => item.statement);
    if (tree.exists(stylePath) && missingImports.length === 0) {
        return;
    }
    const nextContent = [
        ...missingImports,
        content.trimStart(),
    ].filter(Boolean).join('\n');
    if (tree.exists(stylePath)) {
        tree.overwrite(stylePath, nextContent.endsWith('\n') ? nextContent : `${nextContent}\n`);
    }
    else {
        tree.create(stylePath, `${nextContent}\n`);
    }
}
function ensureProjectStyleEntry(buildOptions, stylePath) {
    const styles = Array.isArray(buildOptions['styles']) ? buildOptions['styles'] : [];
    const hasStyle = styles.some((style) => {
        if (typeof style === 'string') {
            return normalizeWorkspacePath(style) === stylePath;
        }
        if (style && typeof style === 'object' && typeof style['input'] === 'string') {
            return normalizeWorkspacePath(style['input']) === stylePath;
        }
        return false;
    });
    if (!hasStyle) {
        styles.unshift(stylePath);
    }
    buildOptions['styles'] = styles;
}
function readJson(tree, path) {
    if (!tree.exists(path)) {
        throw new schematics_1.SchematicsException(`Required file "${path}" was not found.`);
    }
    try {
        return JSON.parse(readText(tree, path));
    }
    catch (error) {
        throw new schematics_1.SchematicsException(`Could not parse "${path}": ${error instanceof Error ? error.message : error}`);
    }
}
function writeJson(tree, path, value) {
    const content = `${JSON.stringify(value, null, 2)}\n`;
    if (tree.exists(path)) {
        tree.overwrite(path, content);
    }
    else {
        tree.create(path, content);
    }
}
function readText(tree, path) {
    const buffer = tree.read(path);
    if (!buffer) {
        throw new schematics_1.SchematicsException(`Could not read "${path}".`);
    }
    return buffer.toString('utf-8');
}
function normalizeWorkspacePath(path) {
    return path.replace(/\\/g, '/').replace(/^\/+/, '');
}
function joinWorkspacePath(...parts) {
    return parts
        .filter(Boolean)
        .join('/')
        .replace(/\/+/g, '/')
        .replace(/^\/+/, '');
}
//# sourceMappingURL=utils.js.map