import * as fs from 'node:fs';
import { join } from 'node:path';
import { ArrayDriver } from '../engine/code-writer/drivers/array.driver.js';
import { DecoratorDriver } from '../engine/code-writer/drivers/decorator.driver.js';
import { ImportDriver } from '../engine/code-writer/drivers/import.driver.js';
import { getInitializer } from '../engine/code-writer/helpers.js';
/** Declare a package module in `app.module.ts`: adds the import and registers it in `@Module({ imports })`. */
export function declareBackPackageModule(ctx, moduleName, importPath) {
    const appModule = ctx.sourceFile('app.module.ts');
    ImportDriver.createImportItems(appModule, importPath, [moduleName]);
    DecoratorDriver.addModuleDecoratorProperty(appModule, 'AppModule', 'Module', 'imports', moduleName);
    appModule.saveSync();
}
/** Add `ScheduleModule.forRoot()` to `app.module.ts` imports if not already present. */
export function addScheduleModule(ctx) {
    const appModule = ctx.sourceFile('app.module.ts');
    ImportDriver.createImportItems(appModule, '@nestjs/schedule', [
        'ScheduleModule',
    ]);
    DecoratorDriver.addModuleDecoratorProperty(appModule, 'AppModule', 'Module', 'imports', 'ScheduleModule.forRoot()');
    appModule.saveSync();
}
/** Regenerate GraphQL type definitions via `npx ts-node generate-typings`. */
export async function runGraphqlTypes(ctx) {
    await ctx.exec('npx', ['ts-node', 'generate-typings']);
}
/**
 * Run the consumer's own Prettier over a list of workspace-relative file paths.
 *
 * Using the consumer's Prettier binary (via `npx prettier --write`) ensures
 * that the consumer's `.prettierrc`, `prettier-plugin-organize-imports`, and
 * trailing-comma settings are honoured. ts-morph emits files with
 * `useTrailingCommas: false` which typically violates `eslint-plugin-prettier/
 * recommended` on the consumer side — a single format pass after `saveSync()`
 * closes the gap without us having to replicate the consumer's Prettier config.
 */
export async function formatWorkspaceFiles(ctx, relativePaths) {
    // Formatting is cosmetic: a failure must not abort an otherwise-correct install.
    // Log a warning and continue so the user can run prettier manually afterwards.
    try {
        await ctx.exec('npx', ['prettier', '--write', ...relativePaths]);
    }
    catch {
        console.warn('[aurora-catalyst] prettier --write failed; files are correct but may not match your formatting config. Run `npx prettier --write` manually.');
    }
}
/** Add an import and an element to a decorator-array property in `shared.module.ts`. */
export function addToSharedModule(sourceFile, propertyName, itemText, importPath, importNames) {
    if (importPath && importNames) {
        ImportDriver.createImportItems(sourceFile, importPath, importNames);
    }
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'SharedModule', 'Module', propertyName, itemText);
}
/** Change the `useClass` of a provider inside a decorator-array property. */
export function changeSharedModuleAdapter(sourceFile, propertyName, provide, adapter, importPath, importNames) {
    ImportDriver.createImportItems(sourceFile, importPath, importNames);
    DecoratorDriver.changeModuleDecoratorPropertyAdapter(sourceFile, 'SharedModule', 'Module', propertyName, provide, adapter);
}
/** Add a `{ provide, useClass }` provider to a decorator array, registering imports first. */
export function addProviderToModule(sourceFile, moduleName, decoratorName, propertyName, provide, useClass, importPath, importNames) {
    ImportDriver.createImportItems(sourceFile, importPath, importNames);
    const args = DecoratorDriver.getClassDecoratorArguments(sourceFile, moduleName, decoratorName);
    const prop = args.getProperty(propertyName);
    if (!prop)
        return;
    const array = getInitializer(prop);
    if (!array)
        return;
    const text = `\n{\n  provide : ${provide},\n  useClass: ${useClass},\n}`;
    ArrayDriver.addArrayItem(sourceFile, text, array);
}
/**
 * Idempotently wire `@bridges/bridges.module.ts` to the golden-reference shape.
 *
 * Adds the required imports and populates the three `@Module` arrays
 * (`imports`, `providers`, `exports`) without duplicating existing entries.
 * Preserves the `@Global()` decorator that the scaffold already ships.
 *
 * Design D3: idempotency is enforced by `DecoratorDriver.addModuleDecoratorProperty`
 * (skip-if-present by `element.getText()` compare) and by `ImportDriver.createImportItems`
 * (additive, deduped by named-import tracking).
 */
export function wireBridgesModule(sourceFile) {
    // ── imports ─────────────────────────────────────────────────────────────────
    ImportDriver.createImportItems(sourceFile, '@api/iam/iam.module', [
        'IamModule',
    ]);
    ImportDriver.createImportItems(sourceFile, '@api/o-auth/o-auth.module', [
        'OAuthModule',
    ]);
    ImportDriver.createImportItems(sourceFile, '@aurora/modules/authentication/domain/ports', ['ACCOUNT_LOADER', 'CREDENTIAL_VERIFIER']);
    ImportDriver.createImportItems(sourceFile, '@bridges/iam', [
        'IAM_BOUNDED_CONTEXT_SEEDER',
        'IAM_CLIENT_READER',
        'IAM_PERMISSION_SEEDER',
    ]);
    ImportDriver.createImportItems(sourceFile, '@bridges/providers', [
        'accountLoaderBridge',
        'credentialVerifierBridge',
        'iamBoundedContextSeederBridge',
        'iamClientReaderBridge',
        'iamPermissionSeederBridge',
    ]);
    // ── @Module.imports ──────────────────────────────────────────────────────────
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'imports', 'OAuthModule');
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'imports', 'IamModule');
    // ── @Module.providers ────────────────────────────────────────────────────────
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'providers', 'iamClientReaderBridge');
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'providers', 'credentialVerifierBridge');
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'providers', 'accountLoaderBridge');
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'providers', 'iamBoundedContextSeederBridge');
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'providers', 'iamPermissionSeederBridge');
    // ── @Module.exports ──────────────────────────────────────────────────────────
    // HR-BRIDGES-001: export ONLY Symbol tokens — never adapter classes.
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'exports', 'IAM_CLIENT_READER');
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'exports', 'CREDENTIAL_VERIFIER');
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'exports', 'ACCOUNT_LOADER');
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'exports', 'IAM_BOUNDED_CONTEXT_SEEDER');
    DecoratorDriver.addModuleDecoratorProperty(sourceFile, 'BridgesModule', 'Module', 'exports', 'IAM_PERMISSION_SEEDER');
    sourceFile.saveSync();
}
/**
 * Author `src/@bridges/providers/index.ts` as a complete barrel re-exporting
 * all five bridge provider consts. Called after both iam and o-auth trees are
 * copied (design D2: the installer owns this file to avoid two partial barrels
 * clobbering on copy). Idempotent: overwrites the file every time (the content
 * is fixed and known).
 */
export function authBridgesProvidersBarrel(workspaceDir) {
    const providersDir = join(workspaceDir, 'src', '@bridges', 'providers');
    fs.mkdirSync(providersDir, { recursive: true });
    const content = [
        "export * from './account-loader.bridge';",
        "export * from './credential-verifier.bridge';",
        "export * from './iam-bounded-context-seeder.bridge';",
        "export * from './iam-client-reader.bridge';",
        "export * from './iam-permission-seeder.bridge';",
        '',
    ].join('\n');
    fs.writeFileSync(join(providersDir, 'index.ts'), content, 'utf8');
}
/**
 * Idempotently set a `KEY = value` line in `<workspaceDir>/.env`.
 *
 * - If the key line exists → replace its value, preserve any inline `# comment`.
 * - If absent → append `KEY = value` at EOF.
 * - Idempotent: re-running yields the same file (design D4).
 */
export function setEnvValue(workspaceDir, key, value) {
    const envPath = join(workspaceDir, '.env');
    const raw = fs.readFileSync(envPath, 'utf8');
    // Escape special RegExp characters in the key so that dots, dashes, etc.
    // are treated as literals and cannot alter the match pattern.
    const escapedKey = key.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw `\$&`);
    // Pattern: match `KEY = <current-value>` optionally followed by ` # anything`
    const lineRegex = new RegExp(`^(${escapedKey}\\s*=\\s*)([^#\\n]*)(#[^\\n]*)?$`, 'm');
    const match = lineRegex.exec(raw);
    if (match) {
        // Replace only the value portion, preserve the inline comment if present.
        const prefix = match[1]; // e.g. "OAUTH_STRATEGY = "
        const comment = match[3] ?? ''; // e.g. "# options: none | aurora-hub | local-provider"
        const newLine = comment
            ? `${prefix}${value} ${comment.trim()}`
            : `${prefix}${value}`;
        // Use the function form of replace() to prevent `$`-based substitution
        // in `newLine` (e.g. a value of "foo$1bar" would otherwise be mangled).
        const updated = raw.replace(lineRegex, () => newLine);
        fs.writeFileSync(envPath, updated, 'utf8');
    }
    else {
        // Key not found — append at EOF (ensure single newline before)
        const separator = raw.endsWith('\n') ? '' : '\n';
        fs.writeFileSync(envPath, `${raw}${separator}${key} = ${value}\n`, 'utf8');
    }
}
