#!/usr/bin/env node

import { Project, SyntaxKind } from 'ts-morph';
import path from 'path';

// Get the root directory of the project where the command is being run
const projectRoot = process.cwd();
const srcRoot = process.argv[2] ?? 'src';
const fullSrcPath = path.join(projectRoot, srcRoot);

const project = new Project({
  // Use the tsconfig from the project running the script
  tsConfigFilePath: path.join(projectRoot, 'tsconfig.json'),
});

function ensureNamedImport(sf, module, identifier) {
  const imp = sf.getImportDeclaration(d => d.getModuleSpecifierValue() === module);
  if (imp) {
    if (!imp.getNamedImports().some(n => n.getName() === identifier)) {
      imp.addNamedImport(identifier);
    }
  } else {
    sf.addImportDeclaration({ moduleSpecifier: module, namedImports: [identifier] });
  }
}

console.log(`Analyzing files in: ${fullSrcPath}`);
project.addSourceFilesAtPaths(path.join(fullSrcPath, '**/*.ts'));

// ─────────────── PRODUCTION FILES ───────────────
project.getSourceFiles().forEach(sf => {
  if (sf.getFilePath().endsWith('.spec.ts')) return;

  sf.getClasses().forEach(cls => {
    const subscribeCalls = cls
      .getDescendantsOfKind(SyntaxKind.CallExpression)
      .filter(
        c =>
          c.getExpression().asKind(SyntaxKind.PropertyAccessExpression)?.getName() === 'subscribe',
      );
    if (!subscribeCalls.length) return;

    console.log(`Updating component: ${cls.getName()} in ${sf.getFilePath()}`);

    ensureNamedImport(sf, 'rxjs', 'Subject');
    ensureNamedImport(sf, 'rxjs/operators', 'takeUntil');
    ensureNamedImport(sf, '@angular/core', 'OnDestroy');

    if (!cls.getImplements().some(i => /OnDestroy\b/.test(i.getText())))
      cls.addImplements('OnDestroy');

    // 1️⃣ Declare or rename destroy subject
    const prop = cls
      .getProperties()
      .find(
        p => p.getType().getText().includes('Subject') && /destroy|unsubscribe/i.test(p.getName()),
      );

    let destroyVar = 'ngUnsubscribe$';

    if (prop) {
      if (prop.getName() === '_$destroy') {
        prop.rename('ngUnsubscribe$');
      }
      prop.setInitializer('new Subject<void>()');
      prop.setType('Subject<void>');
      destroyVar = prop.getName();
    } else {
      cls.insertProperty(0, {
        name: destroyVar,
        scope: 'private',
        type: 'Subject<void>',
        initializer: 'new Subject<void>()',
      });
    }

    // 2️⃣ ngOnDestroy logic
    const teardown = `this.${destroyVar}.next();\n    this.${destroyVar}.complete();`;
    const dtor = cls.getMethod('ngOnDestroy');

    if (dtor) {
      const bodyText = dtor.getBodyText() ?? '';
      if (!bodyText.includes(`this.${destroyVar}.next()`)) {
        dtor.addStatements(teardown);
      }

      dtor
        .getDescendantsOfKind(SyntaxKind.CallExpression)
        .filter(
          c =>
            c.getExpression().asKind(SyntaxKind.PropertyAccessExpression)?.getName() ===
              'unsubscribe' && c.getText().includes(`this.${destroyVar}`),
        )
        .forEach(c => c.getFirstAncestorByKind(SyntaxKind.ExpressionStatement)?.remove());
    } else {
      cls.addMethod({ name: 'ngOnDestroy', statements: teardown });
    }

    // 3️⃣ Add takeUntil
    subscribeCalls.forEach(call => {
      const expr = call.getExpression().asKindOrThrow(SyntaxKind.PropertyAccessExpression);
      const base = expr.getExpression();
      const pipeCall = base.asKind(SyntaxKind.CallExpression);
      const isPipe =
        pipeCall?.getExpression().asKind(SyntaxKind.PropertyAccessExpression)?.getName() === 'pipe';
      const hasTake = pipeCall?.getArguments().some(a => a.getText().includes('takeUntil('));

      if (isPipe && hasTake) return;
      if (isPipe) {
        pipeCall.addArgument(`takeUntil(this.${destroyVar})`);
      } else {
        expr.getExpression().replaceWithText(`${base.getText()}.pipe(takeUntil(this.${destroyVar}))`);
      }
    });
  });

  sf.saveSync();
});

console.log('✅ Transformation complete.');