/**
 * Shared utilities for all backend generators.
 * Single source of truth for dart-defines, launch.json, .env.example, environnements.
 *
 * @module scaffold/shared/generator-utils
 */

const path = require('node:path');
const fs = require('fs-extra');
const { getStrings } = require('./template-strings');
const pkg = require('../../../package.json');

/** Backend IDs: firebase | supabase | api */
const BACKENDS = Object.freeze(['firebase', 'supabase', 'api']);

/**
 * Resolve the default RC keys to bake into launch.json / Makefile.
 *
 * `kasy run` overrides these at runtime based on the device picked (test_ for
 * simulator/emulator, appl_/goog_ for physical). The values here are only used
 * when the user runs Flutter directly (e.g. F5 in VS Code, `make run`) without
 * going through `kasy run`. We default to test_ since that's what works in the
 * iOS Simulator and Android Emulator — the common dev case.
 *
 * Legacy support: projects generated before this refactor only ask for a single
 * Android/iOS key (`rcAndroidKey` / `rcIosKey`). When those are passed we honor
 * them as-is.
 */
function resolveDefaultRcKeys(answers) {
  // Legacy single-key format (pre-test/prod split): keep behavior unchanged.
  if ((answers.rcAndroidKey || answers.rcIosKey) && !answers.rcTestKey && !answers.rcIosProdKey && !answers.rcAndroidProdKey) {
    return {
      android: answers.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY',
      ios: answers.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY',
    };
  }
  const test = (answers.rcTestKey || '').trim();
  const iosProd = (answers.rcIosProdKey || '').trim();
  const androidProd = (answers.rcAndroidProdKey || '').trim();
  return {
    android: test || androidProd || 'YOUR_REVENUECAT_ANDROID_KEY',
    ios: test || iosProd || 'YOUR_REVENUECAT_IOS_KEY',
  };
}

/**
 * Build dart-define args for dev and prod environments.
 * Backend-specific vars: Firebase (none extra), Supabase (BACKEND_URL, SUPABASE_TOKEN), API (BACKEND_URL).
 *
 * @param {'firebase'|'supabase'|'api'} backend
 * @param {string[]} modules - e.g. ['revenuecat','sentry','analytics']
 * @param {object} answers - moduleAnswers + backend-specific (supabaseUrl, supabaseAnonKey, apiBaseUrl)
 * @returns {{ dev: string[], prod: string[] }}
 */
function buildDartDefines(backend, modules, answers) {
  const dev = ['--dart-define=ENV=dev'];
  const prod = ['--dart-define=ENV=prod'];

  if (backend === 'supabase') {
    const url = (answers.supabaseUrl || '').replace(/"/g, '\\"');
    const token = (answers.supabaseAnonKey || '').replace(/"/g, '\\"');
    dev.push(`--dart-define=BACKEND_URL=${url}`);
    dev.push(`--dart-define=SUPABASE_TOKEN=${token}`);
    prod.push(`--dart-define=BACKEND_URL=${url}`);
    prod.push(`--dart-define=SUPABASE_TOKEN=${token}`);
  } else if (backend === 'api') {
    const url = (answers.apiBaseUrl || 'https://YOUR_API_URL.com').replace(/"/g, '\\"');
    dev.push(`--dart-define=BACKEND_URL=${url}`);
    prod.push(`--dart-define=BACKEND_URL=${url}`);
  }

  if (modules.includes('revenuecat')) {
    const { android: androidDefault, ios: iosDefault } = resolveDefaultRcKeys(answers);
    dev.push(`--dart-define=RC_ANDROID_API_KEY=${androidDefault}`);
    dev.push(`--dart-define=RC_IOS_API_KEY=${iosDefault}`);
    prod.push(`--dart-define=RC_ANDROID_API_KEY=${androidDefault}`);
    prod.push(`--dart-define=RC_IOS_API_KEY=${iosDefault}`);
    if (answers.revenuecatWeb) {
      const webKey = answers.rcWebKey || 'YOUR_REVENUECAT_WEB_KEY';
      dev.push(`--dart-define=RC_WEB_API_KEY=${webKey}`);
      prod.push(`--dart-define=RC_WEB_API_KEY=${webKey}`);
    }
  }
  if (modules.includes('sentry')) {
    const dsn = answers.sentryDsn || 'YOUR_SENTRY_DSN';
    prod.push(`--dart-define=SENTRY_DSN=${dsn}`);
  }
  if (modules.includes('analytics')) {
    const token = answers.mixpanelToken || 'YOUR_MIXPANEL_TOKEN';
    dev.push(`--dart-define=MIXPANEL_TOKEN=${token}`);
    prod.push(`--dart-define=MIXPANEL_TOKEN=${token}`);
  }
  if (modules.includes('llm_chat')) {
    let endpoint = answers.llmChatEndpoint || '';
    if (!endpoint) {
      if (backend === 'supabase' && answers.supabaseUrl) {
        endpoint = `${answers.supabaseUrl}/functions/v1/llm-chat`;
      } else if (backend === 'api') {
        endpoint = answers.apiBaseUrl ? `${answers.apiBaseUrl}/llm-chat` : 'YOUR_LLM_ENDPOINT';
      } else {
        // firebase — use the region configured for this project
        const projectId = answers.firebaseProjectId || 'YOUR_PROJECT_ID';
        const region = answers.functionsRegion || 'us-central1';
        endpoint = `https://${region}-${projectId}.cloudfunctions.net/llmChat`;
      }
    }
    dev.push(`--dart-define=LLM_CHAT_ENDPOINT=${endpoint}`);
    prod.push(`--dart-define=LLM_CHAT_ENDPOINT=${endpoint}`);
  }

  return { dev, prod };
}

/**
 * Write .vscode/launch.json with pre-configured dart-define entries.
 *
 * @param {string} projectDir - Project root
 * @param {string} appName - Display name
 * @param {'firebase'|'supabase'|'api'} backend
 * @param {string[]} modules
 * @param {object} answers
 * @param {string} [language='en'] - User's CLI language (en, pt, es)
 */
async function writeVsCodeLaunch(projectDir, appName, backend, modules, answers, language = 'en') {
  const { dev, prod } = buildDartDefines(backend, modules, answers);
  const l = getStrings(language).launch;
  const launchJson = {
    version: '0.2.0',
    configurations: [
      { name: `${appName} (${l.dev})`, request: 'launch', type: 'dart', args: dev },
      { name: `${appName} (${l.prod})`, request: 'launch', type: 'dart', args: prod },
    ],
  };
  await fs.ensureDir(path.join(projectDir, '.vscode'));
  await fs.outputFile(
    path.join(projectDir, '.vscode', 'launch.json'),
    JSON.stringify(launchJson, null, 2) + '\n',
    'utf8'
  );
}

/**
 * Write .env.example as reference for developers.
 *
 * @param {string} projectDir
 * @param {string[]} modules
 * @param {object} answers
 * @param {string} [language='en'] - User's CLI language (en, pt, es)
 */
async function writeEnvExample(projectDir, modules, answers, language = 'en') {
  const t = getStrings(language).envExample;
  const lines = [
    t.generated,
    t.dartDefine,
    t.envSource,
    '',
    t.envDev,
  ];

  // Backend-specific variables
  if (answers.supabaseUrl) {
    lines.push('');
    lines.push(t.supabase);
    lines.push(`BACKEND_URL=${answers.supabaseUrl || 'https://YOUR_PROJECT.supabase.co'}`);
    lines.push(`SUPABASE_TOKEN=${answers.supabaseAnonKey || 'YOUR_SUPABASE_ANON_KEY'}`);
  } else if (answers.apiBaseUrl) {
    lines.push('');
    lines.push(t.apiRest);
    lines.push(`BACKEND_URL=${answers.apiBaseUrl || 'https://YOUR_API_URL.com'}`);
  }

  // Optional module variables
  if (modules.includes('revenuecat')) {
    lines.push('');
    lines.push(t.revenuecat);
    if (t.revenuecatTest) lines.push(t.revenuecatTest);
    lines.push(`RC_TEST_KEY=${(answers.rcTestKey || '').trim()}`);
    if (t.revenuecatProd) lines.push(t.revenuecatProd);
    lines.push(`RC_IOS_PROD_KEY=${(answers.rcIosProdKey || '').trim()}`);
    lines.push(`RC_ANDROID_PROD_KEY=${(answers.rcAndroidProdKey || '').trim()}`);
    if (answers.revenuecatWeb) {
      lines.push(`RC_WEB_API_KEY=${answers.rcWebKey || 'YOUR_REVENUECAT_WEB_KEY'}`);
    }
  }
  if (modules.includes('sentry')) {
    lines.push('');
    lines.push(t.sentry);
    lines.push(`SENTRY_DSN=${answers.sentryDsn || 'YOUR_SENTRY_DSN'}`);
  }
  if (modules.includes('analytics')) {
    lines.push('');
    lines.push(t.mixpanel);
    lines.push(`MIXPANEL_TOKEN=${answers.mixpanelToken || 'YOUR_MIXPANEL_TOKEN'}`);
  }
  if (modules.includes('llm_chat')) {
    lines.push('');
    lines.push(t.llmChat);
    lines.push(`LLM_CHAT_ENDPOINT=${answers.llmChatEndpoint || 'YOUR_FUNCTION_URL'}`);
  }

  await fs.outputFile(path.join(projectDir, '.env.example'), lines.join('\n') + '\n', 'utf8');
}

/**
 * Create .env from .env.example on first scaffold.
 *
 * @param {string} projectDir
 */
async function writeEnvFileIfMissing(projectDir) {
  const envPath = path.join(projectDir, '.env');
  if (await fs.pathExists(envPath)) return;
  const envExamplePath = path.join(projectDir, '.env.example');
  if (!(await fs.pathExists(envExamplePath))) return;
  const content = await fs.readFile(envExamplePath, 'utf8');
  await fs.outputFile(envPath, content, 'utf8');
}

/**
 * Write .firebaserc with project ID.
 *
 * @param {string} projectDir
 * @param {string} firebaseProjectId
 */
async function writeFirebaserc(projectDir, firebaseProjectId) {
  await fs.outputFile(
    path.join(projectDir, '.firebaserc'),
    JSON.stringify({ projects: { default: firebaseProjectId } }, null, 2) + '\n',
    'utf8'
  );
}

/**
 * Write environnements.dart overrides for backend-specific config.
 *
 * @param {string} projectDir
 * @param {'firebase'|'supabase'|'api'} backend
 * @param {Record<string,string>} tokens
 * @param {object} answers - supabaseUrl, supabaseAnonKey, apiBaseUrl
 */
async function writeEnvironnementsOverrides(projectDir, backend, tokens, answers) {
  const envPath = path.join(projectDir, 'lib', 'environnements.dart');
  if (!(await fs.pathExists(envPath))) return;

  let content = await fs.readFile(envPath, 'utf8');

  if (backend === 'supabase') {
    const url = (answers.supabaseUrl || 'https://YOUR_PROJECT.supabase.co').replace(/'/g, "\\'");
    const token = (answers.supabaseAnonKey || 'YOUR_SUPABASE_ANON_KEY').replace(/'/g, "\\'");
    content = content.replace(
      /defaultValue:\s*'https:\/\/supabaseplaceholder\.supabase\.co'/g,
      `defaultValue: '${url}'`
    );
    content = content.replace(
      /defaultValue:\s*'sb_publishable_placeholder'/g,
      `defaultValue: '${token}'`
    );
  } else if (backend === 'api') {
    const url = (answers.apiBaseUrl || 'https://YOUR_API_URL.com').replace(/'/g, "\\'");
    content = content.replace(
      /(\/\/ please replace it with your own backend URL\s*\n\s*defaultValue: )''/,
      `$1'${url}'`
    );
  }

  for (const [from, to] of Object.entries(tokens)) {
    content = content.replaceAll(from, to);
  }
  await fs.outputFile(envPath, content, 'utf8');
}

/**
 * Generate lib/router.dart based on selected modules.
 * Routes and imports are only included for selected modules,
 * eliminating unused code and import errors at compile time.
 *
 * @param {string} projectDir
 * @param {string[]} modules - Selected module IDs
 * @param {string} packageName - Dart package name (e.g. my_app)
 */
async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'basic') {
  const pkg = packageName;
  const withFeedback            = modules.includes('feedback');
  const withLlmChat             = modules.includes('llm_chat');
  const withLocalNotifications  = modules.includes('local_notifications');
  const withOnboarding          = modules.includes('onboarding');
  const withRevenuecat          = modules.includes('revenuecat');
  const withAnalytics           = modules.includes('analytics');

  const fallback = withOnboarding ? '/onboarding' : '/signin';

  const lines = [];

  // ── Imports ────────────────────────────────────────────────────────────────
  lines.push(`import 'package:flutter/material.dart';`);
  lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
  lines.push(`import 'package:go_router/go_router.dart';`);
  lines.push(`import 'package:${pkg}/core/bottom_menu/bottom_menu.dart';`);
  if (withAnalytics) {
    lines.push(`import 'package:${pkg}/core/data/api/analytics_api.dart';`);
  }
  lines.push(`import 'package:${pkg}/core/guards/user_info_guard.dart';`);
  lines.push(`import 'package:${pkg}/core/widgets/page_not_found.dart';`);
  lines.push(`import 'package:${pkg}/features/authentication/ui/phone_auth_page.dart';`);
  lines.push(`import 'package:${pkg}/features/authentication/ui/recover_password_page.dart';`);
  lines.push(`import 'package:${pkg}/features/authentication/ui/signin_page.dart';`);
  lines.push(`import 'package:${pkg}/features/authentication/ui/signup_page.dart';`);
  if (withFeedback) {
    lines.push(`import 'package:${pkg}/features/feedbacks/ui/feedback_page.dart';`);
  }
  if (withLlmChat) {
    lines.push(`import 'package:${pkg}/features/llm_chat/llm_chat_page.dart';`);
  }
  if (withLocalNotifications) {
    lines.push(`import 'package:${pkg}/features/local_reminder/ui/reminder_page.dart';`);
  }
  if (withOnboarding) {
    lines.push(`import 'package:${pkg}/features/onboarding/ui/onboarding_page.dart';`);
  }
  if (withRevenuecat) {
    lines.push(`import 'package:${pkg}/features/subscription/ui/component/premium_page_factory.dart';`);
    lines.push(`import 'package:${pkg}/features/subscription/ui/premium_page.dart';`);
  }

  // ── Provider ──────────────────────────────────────────────────────────────
  lines.push('');
  lines.push(`final goRouterProvider = Provider<GoRouter>((ref) => generateRouter());`);
  lines.push('');
  lines.push(`extension GoRouterRiverpod on Ref {`);
  lines.push(`  GoRouter get goRouter => read(goRouterProvider);`);
  lines.push(`}`);
  lines.push('');
  lines.push(`final navigatorKey = GlobalKey<NavigatorState>();`);
  lines.push('');

  // ── generateRouter ─────────────────────────────────────────────────────────
  lines.push(`GoRouter generateRouter({`);
  lines.push(`  String? initialLocation,`);
  lines.push(`  List<GoRoute>? additionalRoutes,`);
  lines.push(`  List<NavigatorObserver>? observers,`);
  lines.push(`}) {`);
  lines.push(`  return GoRouter(`);
  lines.push(`    initialLocation: '/',`);
  lines.push(`    navigatorKey: navigatorKey,`);
  lines.push(`    errorBuilder: (context, state) => const PageNotFound(),`);
  lines.push(`    observers: [`);
  if (withAnalytics) {
    lines.push(`      AnalyticsObserver(analyticsApi: MixpanelAnalyticsApi.instance()),`);
  }
  lines.push(`      ...?observers,`);
  lines.push(`    ],`);
  lines.push(`    routes: [`);

  // Home
  lines.push(`      GoRoute(`);
  lines.push(`        name: 'home',`);
  lines.push(`        path: '/',`);
  lines.push(`        builder: (context, state) => const UserInfosGuard(`);
  lines.push(`          fallbackRoute: '${fallback}',`);
  lines.push(`          child: BottomMenu(),`);
  lines.push(`        ),`);
  lines.push(`      ),`);

  // Onboarding
  if (withOnboarding) {
    lines.push(`      GoRoute(`);
    lines.push(`        name: 'onboarding',`);
    lines.push(`        path: '/onboarding',`);
    lines.push(`        builder: (context, state) => const OnboardingPage(),`);
    lines.push(`      ),`);
  }

  // Auth (always)
  lines.push(`      GoRoute(`);
  lines.push(`        name: 'signup',`);
  lines.push(`        path: '/signup',`);
  lines.push(`        builder: (context, state) => const SignupPage(),`);
  lines.push(`      ),`);
  lines.push(`      GoRoute(`);
  lines.push(`        name: 'signin',`);
  lines.push(`        path: '/signin',`);
  lines.push(`        builder: (context, state) => const SigninPage(),`);
  lines.push(`      ),`);
  lines.push(`      GoRoute(`);
  lines.push(`        name: 'signinWithPhone',`);
  lines.push(`        path: '/signinWithPhone',`);
  lines.push(`        builder: (context, state) => const PhoneAuthPage(),`);
  lines.push(`      ),`);

  // Premium (revenuecat)
  if (withRevenuecat) {
    const paywallFactory = ['basic', 'minimal', 'withSwitch', 'basicRow'].includes(defaultPaywall)
      ? `PaywallFactory.${defaultPaywall}`
      : 'PaywallFactory.basic';
    lines.push(`      GoRoute(`);
    lines.push(`        name: 'premium',`);
    lines.push(`        path: '/premium',`);
    lines.push(`        builder: (context, state) => const PremiumPage(paywall: ${paywallFactory}),`);
    lines.push(`      ),`);
  }

  // Feedback
  if (withFeedback) {
    lines.push(`      GoRoute(`);
    lines.push(`        name: 'feedback',`);
    lines.push(`        path: '/feedback',`);
    lines.push(`        builder: (context, state) => const FeedbackPage(),`);
    lines.push(`      ),`);
  }

  // LLM Chat
  if (withLlmChat) {
    lines.push(`      GoRoute(`);
    lines.push(`        name: 'assistant',`);
    lines.push(`        path: '/assistant',`);
    lines.push(`        builder: (context, state) => const LlmChatPage(),`);
    lines.push(`      ),`);
  }

  // Local notifications reminder
  if (withLocalNotifications) {
    lines.push(`      GoRoute(`);
    lines.push(`        name: 'reminder',`);
    lines.push(`        path: '/reminder',`);
    lines.push(`        builder: (context, state) => const ReminderPage(),`);
    lines.push(`      ),`);
  }

  // Recover password + 404 (always)
  lines.push(`      GoRoute(`);
  lines.push(`        name: 'recover_password',`);
  lines.push(`        path: '/recover_password',`);
  lines.push(`        builder: (context, state) => const RecoverPasswordPage(),`);
  lines.push(`      ),`);
  lines.push(`      GoRoute(`);
  lines.push(`        name: '404',`);
  lines.push(`        path: '/404',`);
  lines.push(`        builder: (context, state) => const PageNotFound(),`);
  lines.push(`      ),`);

  lines.push(`    ],`);
  lines.push(`  );`);
  lines.push(`}`);

  await fs.ensureDir(path.join(projectDir, 'lib'));
  await fs.outputFile(
    path.join(projectDir, 'lib', 'router.dart'),
    lines.join('\n') + '\n',
    'utf8'
  );
}

/**
 * Write lib/core/config/features.dart with feature flags from user choices.
 *
 * @param {string} projectDir
 * @param {string[]} modules
 * @param {object} [answers={}] - moduleAnswers (revenuecatWeb, etc.)
 * @param {string} [language='en'] - User's CLI language (en, pt, es)
 */
async function writeFeaturesConfig(projectDir, modules, answers = {}, language = 'en') {
  const withOnboarding = modules.includes('onboarding');
  const withLlmChat   = modules.includes('llm_chat');
  const withFeedback            = modules.includes('feedback');
  const withRevenuecat          = modules.includes('revenuecat');
  const withLocalNotifications  = modules.includes('local_notifications');
  const withWeb                 = modules.includes('web');
  const revenuecatWeb = !!(modules.includes('revenuecat') && modules.includes('web') && answers.revenuecatWeb);

  const f = getStrings(language).features;
  const content = `${f.comment1}
${f.comment2}
// ignore_for_file: avoid_redundant_argument_values
const bool withOnboarding = ${withOnboarding};
const bool withLlmChat    = ${withLlmChat};
const bool withFeedback            = ${withFeedback};
const bool withRevenuecat          = ${withRevenuecat};
const bool withLocalNotifications  = ${withLocalNotifications};
${f.comment3}
${f.comment4}
${f.comment5}
${f.comment6}
const bool withWeb = ${withWeb};
${f.comment7}
const bool revenuecatWeb = ${revenuecatWeb};
`;
  await fs.ensureDir(path.join(projectDir, 'lib', 'core', 'config'));
  await fs.outputFile(
    path.join(projectDir, 'lib', 'core', 'config', 'features.dart'),
    content,
    'utf8'
  );
}

/**
 * Write kit_setup.json reflecting user choices (backend, modules).
 *
 * @param {string} projectDir
 * @param {object} options - appName, bundleId, backend, modules, firebaseProjectId, supabaseUrl, supabaseAnonKey
 */
async function writeKitSetup(projectDir, options) {
  const { appName, bundleId, backend, modules = [], firebaseProjectId, supabaseUrl, supabaseAnonKey, moduleAnswers = {} } = options;
  const revenuecatWeb = !!(modules.includes('revenuecat') && modules.includes('web') && moduleAnswers.revenuecatWeb);
  // Booleans tracking which RC keys the user configured. We never persist key
  // values to kit_setup.json — those live in .env (gitignored). These flags
  // let `kasy doctor` warn about release readiness without re-reading .env.
  const revenuecatKeys = modules.includes('revenuecat')
    ? {
        test: !!(moduleAnswers.rcTestKey && moduleAnswers.rcTestKey.trim()),
        iosProd: !!(moduleAnswers.rcIosProdKey && moduleAnswers.rcIosProdKey.trim()),
        androidProd: !!(moduleAnswers.rcAndroidProdKey && moduleAnswers.rcAndroidProdKey.trim()),
      }
    : undefined;
  const config = {
    appName: appName || 'App',
    bundleId: bundleId || 'com.example.app',
    backendProvider: backend || 'firebase',
    functionsRegion: moduleAnswers.functionsRegion || 'us-central1',
    subscriptionModule: modules.includes('revenuecat'),
    feedbackModule: modules.includes('feedback'),
    useFirebaseAuth: true,
    useFirebaseFirestore: backend === 'firebase',
    storageProvider: backend === 'firebase' ? 'firebase' : backend === 'supabase' ? 'supabase' : 'api',
    webCompat: modules.includes('web'),
    revenuecatWeb,
    ...(revenuecatKeys ? { revenuecatKeys } : {}),
    internationalization: true,
    useSentry: modules.includes('sentry'),
    withOnboarding: modules.includes('onboarding'),
    remoteConfigProvider: backend === 'firebase' ? 'firebase' : null,
    analyticsProvider: modules.includes('analytics') ? 'mixpanel' : null,
    anonymousSignup: true,
    withFacebookPixel: modules.includes('facebook'),
    socialAuthList: ['google', 'googlePlay', 'facebook', 'apple'],
    firebaseProjectId: firebaseProjectId || null,
    supabaseProjectId: supabaseUrl ? supabaseUrl.replace(/^https:\/\/([^.]+).*/, '$1') : null,
    withDashboard: false,
    supabasePublishableKey: supabaseAnonKey || null,
    templateVersion: '1.0.0',
    cliVersion: pkg.version,
  };
  await fs.outputFile(
    path.join(projectDir, 'kit_setup.json'),
    JSON.stringify(config, null, 2) + '\n',
    'utf8'
  );
}

/**
 * Write Makefile with run targets for iOS, Android, and default device.
 * Inlines dart-define flags so `make run` works without VS Code.
 *
 * @param {string} projectDir - Project root
 * @param {string} [language='en'] - User's CLI language (en, pt, es)
 * @param {string} [backend] - backend id (firebase|supabase|api)
 * @param {string[]} [modules] - selected modules
 * @param {object} [answers] - user answers (credentials)
 */
async function writeMakefile(projectDir, language = 'en', backend, modules, answers) {
  const m = getStrings(language).makefile;

  let definesList = '';
  if (backend && modules && answers) {
    const { dev } = buildDartDefines(backend, modules, answers);
    if (dev.length > 0) {
      definesList = dev.map((d, i) =>
        i === 0 ? `  ${d} \\` : `  ${d} \\`
      ).join('\n');
      // Remove trailing backslash from last line
      definesList = definesList.replace(/ \\$/, '');
    }
  }

  const definesBlock = definesList
    ? `DEFINES := \\\n${definesList}\n\n`
    : '';
  const runCmd = definesList ? 'flutter run $(DEFINES)' : 'flutter run';
  const runWebCmd = definesList ? 'flutter run -d chrome $(DEFINES)' : 'flutter run -d chrome';

  const content = `${m.comment1}
${m.comment2}
${m.comment3}
${m.comment4}

${definesBlock}.PHONY: run run-ios run-android run-web ${m.releaseIosTarget}

run:
\t${runCmd}

run-ios:
\t${runCmd}

run-android:
\t${runCmd}

run-web:
\t${runWebCmd}

${m.releaseIos}
${m.releaseIosTarget}:
\tbash scripts/release-ios.sh
`;
  await fs.outputFile(path.join(projectDir, 'Makefile'), content, 'utf8');
}

/**
 * Write lib/core/data/api/analytics_api.dart as a no-op stub (no Mixpanel dependency).
 * Called when the user does NOT select the analytics module.
 *
 * @param {string} projectDir
 * @param {string} packageName
 */
async function writeNoOpAnalyticsApi(projectDir, packageName) {
  const pkg = packageName;
  const content = `import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:${pkg}/core/data/models/user.dart';
import 'package:${pkg}/core/initializer/onstart_service.dart';

// No-op analytics. Run: kasy add analytics to activate Mixpanel.
final analyticsApiProvider = Provider<AnalyticsApi>((ref) => const NoOpAnalyticsApi());

abstract class AnalyticsApi implements OnStartService {
  Future<void> logEvent(String name, Map<String, dynamic> params);
  Future<void> logSignout();
  Future<void> identify(User user);
}

class NoOpAnalyticsApi implements AnalyticsApi {
  const NoOpAnalyticsApi();
  @override Future<void> init() async {}
  @override Future<void> logEvent(String name, Map<String, dynamic> params) async {}
  @override Future<void> logSignout() async {}
  @override Future<void> identify(User user) async {}
}

// Stub kept for source-compat with code that still calls MixpanelAnalyticsApi.instance().
// Run: kasy add analytics to swap this file for the real Mixpanel-backed implementation.
class MixpanelAnalyticsApi extends NoOpAnalyticsApi {
  const MixpanelAnalyticsApi._() : super();
  factory MixpanelAnalyticsApi.instance() => const MixpanelAnalyticsApi._();
}

class AnalyticsObserver extends RouteObserver<ModalRoute<dynamic>> {
  final AnalyticsApi analyticsApi;
  final String? prefix;
  AnalyticsObserver({required this.analyticsApi, this.prefix});
  @override void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {}
  @override void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {}
  @override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {}
  @override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {}
  @override void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) {}
  @override void didStopUserGesture() {}
}
`;
  await fs.ensureDir(path.join(projectDir, 'lib', 'core', 'data', 'api'));
  await fs.outputFile(
    path.join(projectDir, 'lib', 'core', 'data', 'api', 'analytics_api.dart'),
    content,
    'utf8'
  );
}

/**
 * Write lib/core/data/api/tracking_api.dart as a no-op stub (no facebook/purchases/sentry deps).
 * Called when the user does NOT select the facebook module.
 *
 * @param {string} projectDir
 * @param {string} packageName
 */
async function writeNoOpTrackingApi(projectDir, packageName) {
  const pkg = packageName;
  const content = `import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:${pkg}/core/initializer/onstart_service.dart';
import 'package:${pkg}/core/states/models/user_state.dart';
import 'package:${pkg}/core/states/user_state_notifier.dart';
import 'package:${pkg}/features/notifications/repositories/device_repository.dart';

// No-op tracking. Run: kasy add facebook to activate Facebook Events.
final facebookEventApiProvider = Provider(
  (ref) => FacebookEventApi(
    deviceRepository: ref.watch(deviceRepositoryProvider),
    userState: ref.watch(userStateNotifierProvider),
  ),
);

class FacebookEventApi implements OnStartService {
  final DeviceRepository deviceRepository;
  final UserState userState;
  FacebookEventApi({required this.deviceRepository, required this.userState});
  @override Future<void> init() async {}
  Future<void> initUser(String userId) async {}
  Future<void> requestIDFA() async {}
  Future<void> logMetaStartTrial(String orderId, double price, String currency) async {}
  Future<void> logMetaSubscribe(String orderId, double price, String currency) async {}
}
`;
  await fs.ensureDir(path.join(projectDir, 'lib', 'core', 'data', 'api'));
  await fs.outputFile(
    path.join(projectDir, 'lib', 'core', 'data', 'api', 'tracking_api.dart'),
    content,
    'utf8'
  );
}

/**
 * Generate lib/main.dart based on backend + selected modules.
 *
 * @param {string} projectDir
 * @param {'firebase'|'supabase'|'api'} backend
 * @param {string[]} modules
 * @param {string} packageName
 * @param {object} [options={}] - supabaseUrl, supabaseAnonKey
 */
async function writeMainDart(projectDir, backend, modules, packageName, options = {}) {
  const pkg = packageName;
  const withSentry = modules.includes('sentry');
  const withWidget = modules.includes('widget');
  const withRevenuecat = modules.includes('revenuecat');
  const withSupabase = backend === 'supabase';
  const withFirebase = backend === 'firebase' || backend === 'supabase'; // Firebase used for auth/notifications in all backends currently

  const lines = [];

  // ── Imports ────────────────────────────────────────────────────────────────
  if (withFirebase) {
    lines.push(`import 'package:firebase_core/firebase_core.dart';`);
  }
  lines.push(`import 'package:flutter/foundation.dart';`);
  lines.push(`import 'package:flutter/material.dart';`);
  lines.push(`import 'package:flutter/services.dart';`);
  lines.push(`import 'package:flutter_localizations/flutter_localizations.dart';`);
  lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
  lines.push(`import 'package:logger/logger.dart';`);
  lines.push(`import 'package:${pkg}/core/config/app_env.dart';`);
  lines.push(`import 'package:${pkg}/core/data/api/analytics_api.dart';`);
  lines.push(`import 'package:${pkg}/core/data/api/remote_config_api.dart';`);
  lines.push(`import 'package:${pkg}/core/data/api/tracking_api.dart';`);
  if (withWidget) {
    lines.push(`import 'package:${pkg}/core/home_widgets/home_widget_service.dart';`);
  }
  lines.push(`import 'package:${pkg}/core/initializer/onstart_widget.dart';`);
  lines.push(`import 'package:${pkg}/core/shared_preferences/shared_preferences.dart';`);
  lines.push(`import 'package:${pkg}/core/states/user_state_notifier.dart';`);
  lines.push(`import 'package:${pkg}/core/theme/theme.dart';`);
  lines.push(`import 'package:${pkg}/environnements.dart';`);
  if (withFirebase) {
    lines.push(`import 'package:${pkg}/firebase_options_dev.dart' as firebase_dev;`);
  }
  lines.push(`import 'package:${pkg}/i18n/translations.g.dart';`);
  lines.push(`import 'package:${pkg}/features/authentication/api/authentication_api.dart';`);
  lines.push(`import 'package:${pkg}/features/notifications/api/local_notifier.dart';`);
  lines.push(`import 'package:${pkg}/features/notifications/repositories/notifications_repository.dart';`);
  if (withRevenuecat) {
    lines.push(`import 'package:${pkg}/features/subscription/repositories/subscription_repository.dart';`);
  }
  lines.push(`import 'package:${pkg}/router.dart';`);
  if (withSentry) {
    lines.push(`import 'package:sentry_flutter/sentry_flutter.dart';`);
  }
  lines.push(`import 'package:shared_preferences/shared_preferences.dart';`);
  if (withSupabase) {
    lines.push(`import 'package:supabase_flutter/supabase_flutter.dart';`);
  }
  lines.push('');

  // ── main() ────────────────────────────────────────────────────────────────
  lines.push(`void main() async {`);
  lines.push(`  WidgetsFlutterBinding.ensureInitialized();`);
  lines.push(`  await AppEnv.load();`);
  lines.push(`  final env = Environment.fromEnv();`);
  lines.push(`  final logger = Logger();`);
  lines.push(`  logger.i('Starting app in \${env.name} mode');`);
  lines.push(`  // I like to force portrait mode on mobile devices`);
  lines.push(`  // What is the last time you used an app in landscape mode?`);
  lines.push(`  SystemChrome.setPreferredOrientations([`);
  lines.push(`    DeviceOrientation.portraitUp,`);
  lines.push(`    DeviceOrientation.portraitDown,`);
  lines.push(`  ]);`);
  lines.push('');
  lines.push(`  // initialize shared preferences for theme and locale`);
  lines.push(`  final sharedPrefs = await SharedPreferences.getInstance();`);
  lines.push('');
  lines.push(`  // Restore saved locale; fall back to device locale if none saved.`);
  lines.push(`  // Supported locales: en, pt, es. If the device locale is not one of these,`);
  lines.push(`  // the base locale (en) is used as fallback (configured in slang.yaml).`);
  lines.push(`  // To change the default locale, update \`base_locale\` in slang.yaml.`);
  lines.push(`  final savedLocale = sharedPrefs.getString('app_locale');`);
  lines.push(`  if (savedLocale != null) {`);
  lines.push(`    final appLocale = AppLocale.values.firstWhere(`);
  lines.push(`      (l) => l.languageCode == savedLocale,`);
  lines.push(`      orElse: () => AppLocale.en,`);
  lines.push(`    );`);
  lines.push(`    LocaleSettings.setLocale(appLocale);`);
  lines.push(`  } else {`);
  lines.push(`    LocaleSettings.useDeviceLocale();`);
  lines.push(`  }`);
  lines.push('');

  if (withSupabase) {
    lines.push(`  // initialize Supabase`);
    lines.push(`  await Supabase.initialize(`);
    lines.push(`    url: AppEnv.backendUrl,`);
    lines.push(`    anonKey: AppEnv.supabaseToken,`);
    lines.push(`  );`);
    lines.push('');
  }

  if (withFirebase) {
    lines.push(`  // initialize firebase app for notifications`);
    lines.push(`  await switch (env) {`);
    lines.push(`    DevEnvironment() => Firebase.initializeApp(`);
    lines.push(`      options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
    lines.push(`    ),`);
    lines.push(`    ProdEnvironment() => Firebase.initializeApp(`);
    lines.push(`      // TODO replace with your own firebase options for production environment (if needed)`);
    lines.push(`      options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
    lines.push(`    ),`);
    lines.push(`  };`);
    lines.push('');
  }

  if (withSentry) {
    lines.push(`  // initialize sentry for error reporting in production only`);
    lines.push(`  // run the app with Sentry for production environment`);
    lines.push(`  if (env is DevEnvironment) {`);
    lines.push(`    run(sharedPrefs);`);
    lines.push(`  } else if (env is ProdEnvironment) {`);
    lines.push(`    SentryFlutter.init((options) {`);
    lines.push(`      options.dsn = env.sentryDsn;`);
    lines.push(`      // 20% of traces will be sent to Sentry server. You should start with 1 and decrease it once you have more users.`);
    lines.push(`      options.tracesSampleRate = 0.2;`);
    lines.push(`      options.environment = env.name;`);
    lines.push(`    }, appRunner: () => run(sharedPrefs));`);
    lines.push(`  }`);
  } else {
    lines.push(`  run(sharedPrefs);`);
  }

  lines.push(`}`);
  lines.push('');

  // ── run() ────────────────────────────────────────────────────────────────
  lines.push(`void run(SharedPreferences prefs) => runApp(`);
  lines.push(`  TranslationProvider(`);
  lines.push(`    child: ProviderScope(child: MyApp(sharedPreferences: prefs)),`);
  lines.push(`  ),`);
  lines.push(`);`);
  lines.push('');

  // ── MyApp widget ─────────────────────────────────────────────────────────
  lines.push(`// use this if you want to define different themes for different platforms`);
  lines.push(`// notifier: AppTheme.adaptive(`);
  lines.push(`//   defaultTextTheme: KasyTextTheme.build(),`);
  lines.push(`//   ios: const IosThemeFactory(),`);
  lines.push(`//   android: const AndroidThemeFactory(),`);
  lines.push(`//   web: const WebThemeFactory(),`);
  lines.push(`//   lightColors: KasyColors.light(),`);
  lines.push(`//   darkColors: KasyColors.dark(),`);
  lines.push(`//   mode: ThemeMode.dark,`);
  lines.push(`// ),`);
  lines.push(`// See ./docs/theme.md for more details`);
  lines.push(`class MyApp extends ConsumerWidget {`);
  lines.push(`  final SharedPreferences sharedPreferences;`);
  lines.push('');
  lines.push(`  const MyApp({super.key, required this.sharedPreferences});`);
  lines.push('');
  lines.push(`  // This widget is the root of your application.`);
  lines.push(`  @override`);
  lines.push(`  Widget build(BuildContext context, WidgetRef ref) {`);
  lines.push(`    ErrorWidget.builder = (FlutterErrorDetails details) {`);
  lines.push(`      return AppErrorWidget(error: details);`);
  lines.push(`    };`);
  lines.push(`    final goRouter = ref.watch(goRouterProvider);`);
  lines.push('');
  lines.push(`    return ThemeProvider(`);
  lines.push(`      notifier: AppTheme.uniform(`);
  lines.push(`        sharedPreferences: sharedPreferences,`);
  lines.push(`        themeFactory: const UniversalThemeFactory(),`);
  lines.push(`        lightColors: KasyColors.light(),`);
  lines.push(`        darkColors: KasyColors.dark(),`);
  lines.push(`        textTheme: KasyTextTheme.build(),`);
  lines.push(`        defaultMode: ThemeMode.light,`);
  lines.push(`      ),`);
  lines.push(`      child: Builder(`);
  lines.push(`        builder: (context) {`);
  lines.push(`          return MaterialApp.router(`);
  lines.push(`            title: 'Kasy',`);
  lines.push(`            theme: ThemeProvider.of(context).light,`);
  lines.push(`            darkTheme: ThemeProvider.of(context).dark,`);
  lines.push(`            themeMode: ThemeProvider.of(context).mode,`);
  lines.push(`            routerConfig: goRouter,`);
  lines.push(`            localizationsDelegates: const [`);
  lines.push(`              GlobalMaterialLocalizations.delegate,`);
  lines.push(`              GlobalWidgetsLocalizations.delegate,`);
  lines.push(`              GlobalCupertinoLocalizations.delegate,`);
  lines.push(`            ],`);
  lines.push(`            locale: TranslationProvider.of(context).flutterLocale,`);
  lines.push(`            supportedLocales: AppLocaleUtils.supportedLocales,`);
  lines.push(`            // Initializer is a widget that allows us to run some code before the app is ready`);
  lines.push(`            builder: (context, child) => Initializer(`);
  lines.push(`              services: [`);
  lines.push(`                authenticationApiProvider,`);
  lines.push(`                // shared preferences must be loaded`);
  lines.push(`                sharedPreferencesProvider,`);
  lines.push(`                // remote config api`);
  lines.push(`                remoteConfigApiProvider,`);
  lines.push(`                // notifications`);
  lines.push(`                notificationsSettingsProvider,`);
  lines.push(`                notificationRepositoryProvider,`);
  lines.push(`                // user state`);
  if (withRevenuecat) {
    lines.push(`                subscriptionRepositoryProvider,`);
  }
  lines.push(`                userStateNotifierProvider.notifier,`);
  if (withWidget) {
    lines.push(`                homeWidgetsManagerProvider,`);
  }
  lines.push(`                // analytics`);
  lines.push(`                analyticsApiProvider,`);
  lines.push(`                facebookEventApiProvider,`);
  lines.push(`              ],`);
  lines.push(`              onReady: child!,`);
  lines.push(`              onError: (_, error) => InitializationErrorPage(error: error),`);
  lines.push(`              onLoading: Scaffold(`);
  lines.push(`                body: Center(`);
  lines.push(`                  child: CircularProgressIndicator.adaptive(`);
  lines.push(`                    valueColor: AlwaysStoppedAnimation<Color>(`);
  lines.push(`                      context.colors.primary,`);
  lines.push(`                    ),`);
  lines.push(`                  ),`);
  lines.push(`                ),`);
  lines.push(`              ),`);
  lines.push(`            ),`);
  lines.push(`          );`);
  lines.push(`        },`);
  lines.push(`      ),`);
  lines.push(`    );`);
  lines.push(`  }`);
  lines.push(`}`);
  lines.push('');

  // ── AppErrorWidget ────────────────────────────────────────────────────────
  lines.push(`/// This is an example of a more user friendly error widget`);
  lines.push(`/// By default Flutter will show a red screen with the error in debug mode`);
  lines.push(`/// and a grey screen in release mode`);
  lines.push(`class AppErrorWidget extends StatelessWidget {`);
  lines.push(`  final FlutterErrorDetails? error;`);
  lines.push('');
  lines.push(`  const AppErrorWidget({super.key, this.error});`);
  lines.push('');
  lines.push(`  @override`);
  lines.push(`  Widget build(BuildContext context) {`);
  lines.push(`    return Container(`);
  lines.push(`      padding: const EdgeInsets.all(16),`);
  lines.push(`      color: Colors.orangeAccent,`);
  lines.push(`      child: Column(`);
  lines.push(`        mainAxisAlignment: MainAxisAlignment.center,`);
  lines.push(`        children: [`);
  lines.push(`          const Text(`);
  lines.push(`            'Oups!',`);
  lines.push(`            style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),`);
  lines.push(`          ),`);
  lines.push(`          const SizedBox(height: 8),`);
  lines.push(`          const Text(`);
  lines.push(`            'Sorry, Something went wrong',`);
  lines.push(`            style: TextStyle(color: Colors.white),`);
  lines.push(`          ),`);
  lines.push(`          const SizedBox(height: 8),`);
  lines.push(`          Text(`);
  lines.push(`            '\${error?.exception}\\n',`);
  lines.push(`            style: const TextStyle(color: Colors.white, fontSize: 10),`);
  lines.push(`          ),`);
  lines.push(`        ],`);
  lines.push(`      ),`);
  lines.push(`    );`);
  lines.push(`  }`);
  lines.push(`}`);
  lines.push('');

  // ── InitializationErrorPage ───────────────────────────────────────────────
  lines.push(`class InitializationErrorPage extends StatelessWidget {`);
  lines.push(`  final String error;`);
  lines.push('');
  lines.push(`  const InitializationErrorPage({super.key, required this.error});`);
  lines.push('');
  lines.push(`  @override`);
  lines.push(`  Widget build(BuildContext context) {`);
  lines.push(`    return Scaffold(`);
  lines.push(`      body: Padding(`);
  lines.push(`        padding: const EdgeInsets.all(24.0),`);
  lines.push(`        child: Column(`);
  lines.push(`          mainAxisAlignment: MainAxisAlignment.center,`);
  lines.push(`          crossAxisAlignment: CrossAxisAlignment.stretch,`);
  lines.push(`          spacing: 8,`);
  lines.push(`          children: [`);
  lines.push(`            Text('Cannot start app', style: context.textTheme.titleLarge),`);
  lines.push(`            Text(`);
  lines.push(`              'Check your internet connection and start again',`);
  lines.push(`              style: context.textTheme.bodyLarge?.copyWith(`);
  lines.push(`                color: context.colors.muted,`);
  lines.push(`              ),`);
  lines.push(`            ),`);
  lines.push(`            if (kDebugMode)`);
  lines.push(`              Text(`);
  lines.push(`                "developper mode error: \$error",`);
  lines.push(`                style: context.textTheme.bodyLarge?.copyWith(`);
  lines.push(`                  color: context.colors.error,`);
  lines.push(`                ),`);
  lines.push(`              ),`);
  lines.push(`          ],`);
  lines.push(`        ),`);
  lines.push(`      ),`);
  lines.push(`    );`);
  lines.push(`  }`);
  lines.push(`}`);
  lines.push('');

  await fs.ensureDir(path.join(projectDir, 'lib'));
  await fs.outputFile(
    path.join(projectDir, 'lib', 'main.dart'),
    lines.join('\n'),
    'utf8'
  );
}

/**
 * Remove optional module directories that were NOT selected by the user.
 * The base template (Firebase/) has all modules; this function removes the
 * directories for modules the user did not select to avoid dead code.
 *
 * @param {string} projectDir
 * @param {string[]} modules - Selected modules (e.g. ['analytics', 'sentry'])
 */
async function removeModuleDirs(projectDir, modules) {
  const removable = [
    { module: 'feedback',  dir: path.join('lib', 'features', 'feedbacks') },
    { module: 'llm_chat',  dir: path.join('lib', 'features', 'llm_chat') },
    { module: 'onboarding',dir: path.join('lib', 'features', 'onboarding') },
    // home_widgets/ imports home_widget package — remove when widget not selected
    { module: 'widget',    dir: path.join('lib', 'core', 'home_widgets') },
    // subscription/ imports purchases_flutter — remove when revenuecat not selected
    { module: 'revenuecat', dir: path.join('lib', 'features', 'subscription') },
    // local_reminder/ uses flutter_timezone/timezone — remove when local_notifications not selected
    { module: 'local_notifications', dir: path.join('lib', 'features', 'local_reminder') },
  ];

  for (const { module, dir } of removable) {
    if (!modules.includes(module)) {
      const fullPath = path.join(projectDir, dir);
      if (await fs.pathExists(fullPath)) {
        await fs.remove(fullPath);
      }
    }
  }
}

/**
 * Remove Android native widget artifacts when the widget module is not selected.
 * Deletes MyWidget.kt, MyWidgetReceiver.kt, mywidget_info.xml, and removes
 * the <receiver> block from AndroidManifest.xml so the widget does not appear
 * in the Android widget picker.
 *
 * @param {string} projectDir
 * @param {string} bundleId - e.g. "com.example.myapp"
 */
/**
 * Removes Facebook SDK meta-data entries from AndroidManifest.xml when the
 * Facebook module is not selected. Without this, the Facebook SDK initializes
 * on app start and logs OAuthException error 190 (invalid App ID) for every
 * network request.
 *
 * @param {string} projectDir
 */
/**
 * Remove FacebookSigninComponent from signin_page.dart and signup_page.dart
 * when the facebook module is not selected.
 * Removes both the import line and the widget line (package-name-agnostic regex).
 *
 * @param {string} projectDir
 */
async function removeFacebookSigninFromAuthPages(projectDir) {
  const pages = [
    path.join(projectDir, 'lib', 'features', 'authentication', 'ui', 'signin_page.dart'),
    path.join(projectDir, 'lib', 'features', 'authentication', 'ui', 'signup_page.dart'),
  ];
  for (const p of pages) {
    if (!(await fs.pathExists(p))) continue;
    let content = await fs.readFile(p, 'utf8');
    // Remove import line
    content = content.replace(/^import 'package:[^']+\/features\/authentication\/ui\/components\/facebook_signin\.dart';\n/m, '');
    // Remove component line (any leading whitespace)
    content = content.replace(/[ \t]*const FacebookSigninComponent\(\),\n/g, '');
    await fs.writeFile(p, content, 'utf8');
  }
}

async function removeAndroidFacebookMetadata(projectDir) {
  const manifestPath = path.join(projectDir, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
  if (!(await fs.pathExists(manifestPath))) return;

  let content = await fs.readFile(manifestPath, 'utf8');
  // Remove both facebook meta-data lines (ApplicationId and ClientToken)
  content = content.replace(/[ \t]*<meta-data android:name="com\.facebook\.sdk\.ApplicationId"[^\n]*\n?/g, '');
  content = content.replace(/[ \t]*<meta-data android:name="com\.facebook\.sdk\.ClientToken"[^\n]*\n?/g, '');
  await fs.writeFile(manifestPath, content, 'utf8');
}

async function removeAndroidWidgetArtifacts(projectDir, bundleId) {
  const bundlePath = (bundleId || '').replace(/\./g, '/');
  const kotlinDir = path.join(projectDir, 'android', 'app', 'src', 'main', 'kotlin', bundlePath);

  for (const file of ['MyWidget.kt', 'MyWidgetReceiver.kt']) {
    const p = path.join(kotlinDir, file);
    if (await fs.pathExists(p)) await fs.remove(p);
  }

  const widgetXml = path.join(projectDir, 'android', 'app', 'src', 'main', 'res', 'xml', 'mywidget_info.xml');
  if (await fs.pathExists(widgetXml)) await fs.remove(widgetXml);

  const manifestPath = path.join(projectDir, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
  if (await fs.pathExists(manifestPath)) {
    let content = await fs.readFile(manifestPath, 'utf8');
    // Remove the MyWidgetReceiver <receiver> block (multiline)
    content = content.replace(/[ \t]*<receiver[\s\S]*?MyWidgetReceiver[\s\S]*?<\/receiver>\n?/m, '');
    await fs.writeFile(manifestPath, content, 'utf8');
  }
}

/**
 * Writes a NoOp admin_home_widgets.dart when the widget module is not selected.
 * The real version imports home_widget_mywidget_service.dart which won't exist.
 *
 * @param {string} projectDir
 */
async function writeNoOpAdminHomeWidgets(projectDir) {
  const content = `import 'package:flutter/material.dart';
// No-op home widgets admin panel. Run: kasy add widget to activate.
class AdminHomeWidgets extends StatelessWidget {
  const AdminHomeWidgets({super.key});

  @override
  Widget build(BuildContext context) => const SizedBox.shrink();
}
`;
  await fs.outputFile(
    path.join(projectDir, 'lib', 'features', 'settings', 'ui', 'components', 'admin', 'admin_home_widgets.dart'),
    content,
    'utf8',
  );
}

/**
 * Writes a NoOp feature_request_repository.dart when the feedback module is not selected.
 * premium_page_provider.dart imports this provider — without it the project won't compile.
 *
 * @param {string} projectDir
 * @param {string} packageName
 */
async function writeNoOpFeatureRequestRepository(projectDir, packageName) {
  const content = `import 'package:flutter_riverpod/flutter_riverpod.dart';
// No-op feedback repository. Run: kasy add feedback to activate.
final featureRequestRepositoryProvider = Provider<FeatureRequestRepository>(
  (ref) => const FeatureRequestRepository(),
);

class FeatureRequestRepository {
  const FeatureRequestRepository();

  Future<List<dynamic>> getActiveFeatureRequests() async => [];
  Future<List<dynamic>> getUserVotes(String userId) async => [];
  Future<void> createNewFeatureSuggestion({
    required String userId,
    required String title,
    required String description,
  }) async {}
}

class UserAlreadyVotedException implements Exception {
  final String? message;
  UserAlreadyVotedException({this.message});
}
`;
  await fs.outputFile(
    path.join(projectDir, 'lib', 'features', 'feedbacks', 'repositories', 'feature_request_repository.dart'),
    content,
    'utf8',
  );
}

/**
 * Strip optional pubspec.yaml dependencies that are not needed based on selected modules.
 * Reads pubspec.yaml as text and removes lines matching "  <dep>: ..." for each dep.
 *
 * @param {string} projectDir
 * @param {string[]} modules - Selected modules
 */
async function stripPubspecDeps(projectDir, modules) {
  const pubspecPath = path.join(projectDir, 'pubspec.yaml');
  if (!(await fs.pathExists(pubspecPath))) return;

  // Map: module → list of dep package names to remove when module NOT selected.
  const depMap = [
    { module: 'analytics', deps: ['mixpanel_flutter'] },
    { module: 'revenuecat', deps: ['purchases_flutter'] },
    { module: 'widget',    deps: ['home_widget', 'background_fetch'] },
  ];

  let content = await fs.readFile(pubspecPath, 'utf8');

  for (const { module, deps } of depMap) {
    if (!modules.includes(module)) {
      for (const dep of deps) {
        // Remove lines like "  mixpanel_flutter: ^2.5.0" (with any amount of leading spaces)
        content = content.replace(new RegExp(`^[ \\t]*${dep}:.*\\n`, 'gm'), '');
      }
    }
  }

  // sentry_flutter can be stripped only when none of the modules that use it are selected:
  // - revenuecat: revenuecat tracking_api.dart uses Sentry
  // - facebook: facebook tracking_api.dart uses Sentry
  // - sentry: explicitly selected
  // When none of these are selected, writeNoOpSentryUsages patches core files so they
  // no longer import sentry_flutter, allowing the dep to be removed.
  if (!modules.includes('sentry') && !modules.includes('revenuecat') && !modules.includes('facebook')) {
    content = content.replace(new RegExp(`^[ \\t]*sentry_flutter:.*\\n`, 'gm'), '');
  }

  await fs.outputFile(pubspecPath, content, 'utf8');
}

/**
 * Patches files that always import sentry_flutter, removing the dependency
 * when neither sentry, revenuecat, nor facebook modules are selected.
 *
 * @param {string} projectDir
 */
async function writeNoOpSentryUsages(projectDir) {
  const sentryImport = `import 'package:sentry_flutter/sentry_flutter.dart';\n`;

  const files = [
    path.join(projectDir, 'lib', 'core', 'initializer', 'onstart_widget.dart'),
    path.join(projectDir, 'lib', 'core', 'data', 'api', 'remote_config_api.dart'),
    path.join(projectDir, 'lib', 'features', 'notifications', 'api', 'local_notifier.dart'),
    path.join(projectDir, 'lib', 'features', 'notifications', 'providers', 'models', 'notification.dart'),
    path.join(projectDir, 'lib', 'features', 'notifications', 'shared', 'notification_permission_bottom_sheet.dart'),
  ];

  for (const filePath of files) {
    if (!(await fs.pathExists(filePath))) continue;
    let content = await fs.readFile(filePath, 'utf8');
    content = content.replace(sentryImport, '');
    content = content.replace(/^[ \t]*Sentry\.captureException\([^)]*\);\n/gm, '');
    await fs.outputFile(filePath, content, 'utf8');
  }
}

/**
 * Adds a dependency to pubspec.yaml if not already present.
 * Inserts alphabetically within the dependencies block.
 *
 * @param {string} projectDir
 * @param {string} depName - e.g. 'mixpanel_flutter'
 * @param {string} depVersion - e.g. '^2.5.0'
 */
async function addPubspecDep(projectDir, depName, depVersion) {
  const pubspecPath = path.join(projectDir, 'pubspec.yaml');
  if (!(await fs.pathExists(pubspecPath))) return;

  let content = await fs.readFile(pubspecPath, 'utf8');

  // Skip if already present
  if (new RegExp(`^[ \\t]*${depName}:`, 'm').test(content)) return;

  const newLine = `  ${depName}: ${depVersion}`;

  // Find the dependencies block and insert alphabetically
  const lines = content.split('\n');
  let inDeps = false;
  let insertAt = -1;

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    if (/^dependencies:/.test(line)) {
      inDeps = true;
      continue;
    }
    if (inDeps) {
      // End of dependencies block
      if (/^[a-zA-Z]/.test(line) || /^dev_dependencies:/.test(line)) {
        if (insertAt === -1) insertAt = i; // fallback: insert just before this
        break;
      }
      // Only consider simple dep lines (2 spaces + name:)
      const match = line.match(/^  ([a-z_][a-z_0-9]*)\s*:/);
      if (match) {
        if (match[1] > depName) {
          insertAt = i;
          break;
        }
        insertAt = i + 1; // advance past this dep
      }
    }
  }

  if (insertAt === -1) {
    // Fallback: append before dev_dependencies
    const devIdx = lines.findIndex(l => /^dev_dependencies:/.test(l));
    insertAt = devIdx !== -1 ? devIdx : lines.length;
  }

  lines.splice(insertAt, 0, newLine);
  await fs.outputFile(pubspecPath, lines.join('\n'), 'utf8');
}

/**
 * Writes no-op stubs for the subscription module when revenuecat is not selected.
 * Removes purchases_flutter-dependent test files and creates minimal stubs so that
 * core files that reference subscription types still compile.
 *
 * @param {string} projectDir
 * @param {string} packageName
 */
async function writeNoOpSubscriptionStubs(projectDir, packageName) {
  const pkg = packageName;

  // 0. Replace lib/core/data/models/subscription.dart with stripped version (no purchases_flutter)
  await fs.outputFile(
    path.join(projectDir, 'lib', 'core', 'data', 'models', 'subscription.dart'),
    `import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'subscription.freezed.dart';

abstract class SubscriptionProduct {
  String get skuId;
  String get id;
  String get description;
  String get label;
  double get price;
  Duration get duration;
  String? get promotion;
  String formattedPrice(BuildContext context);
  DurationType get durationType;
  String? get title;
  int? get trialDays;
  List<String>? get features;
  String get priceString;
  String get currency;
  String pricePerMonth(BuildContext context);
  String? pricePerYear(BuildContext context);
}

// RevenueCatProduct is provided by the revenuecat feature module.
// Run: kasy add revenuecat to activate in-app purchases.

enum DurationType { week, month, threeMonth, sixMonth, year, lifetime }

@freezed
sealed class Subscription with _$Subscription {
  const factory Subscription.active({
    SubscriptionProduct? activeOffer,
  }) = SubscriptionStateData;

  const factory Subscription.inactive({
    required int hoursBetweenTwoRequests,
    DateTime? lastAskingDate,
  }) = SubscriptionInactiveStateData;

  const factory Subscription.loading() = SubscriptionStateLoading;

  const Subscription._();

  bool get canPurchase => switch (this) {
    SubscriptionStateData() => false,
    SubscriptionInactiveStateData() => true,
    SubscriptionStateLoading() => false,
  };

  bool get isActive => switch (this) {
    SubscriptionStateData() => true,
    SubscriptionInactiveStateData() => false,
    SubscriptionStateLoading() => false,
  };

  // Returns false without RevenueCat — run: kasy add revenuecat to activate
  bool get isInTrial => false;

  // Returns false without RevenueCat — run: kasy add revenuecat to activate
  bool get hasRenewal => false;

  bool get isLifetime => switch (this) {
    SubscriptionStateData(:final activeOffer) =>
      activeOffer?.durationType == DurationType.lifetime,
    SubscriptionInactiveStateData() => false,
    SubscriptionStateLoading() => false,
  };

  bool get shouldAskForSubscription {
    final now = DateTime.now();
    final (lastAskingDate, hoursBetweenTwoRequests) = switch (this) {
      SubscriptionStateData() => (null, null),
      SubscriptionInactiveStateData(
        :final lastAskingDate,
        :final hoursBetweenTwoRequests,
      ) =>
        (lastAskingDate, hoursBetweenTwoRequests),
      SubscriptionStateLoading() => (null, null),
    };
    if (lastAskingDate == null) {
      return true;
    }
    final diff = now.difference(lastAskingDate);
    return diff.inHours >= hoursBetweenTwoRequests!;
  }
}
`,
    'utf8',
  );

  // 0b. Strip EntitlementInfo from user_state_notifier.dart (purchases_flutter must be removed)
  const userStateFile = path.join(projectDir, 'lib', 'core', 'states', 'user_state_notifier.dart');
  if (await fs.pathExists(userStateFile)) {
    let userStateContent = await fs.readFile(userStateFile, 'utf8');
    userStateContent = userStateContent
      .replaceAll("import 'package:purchases_flutter/models/entitlement_info_wrapper.dart';\n", '')
      .replaceAll('\n    List<EntitlementInfo>? entitlements,', '')
      .replace(/,\n\s+entitlements: entitlements,/g, ',');
    await fs.writeFile(userStateFile, userStateContent, 'utf8');
  }

  // 1. No-op subscription_repository.dart (used by user_repository, onboarding, home_widget_background_task)
  await fs.outputFile(
    path.join(projectDir, 'lib', 'features', 'subscription', 'repositories', 'subscription_repository.dart'),
    `import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:${pkg}/core/data/models/subscription.dart';
import 'package:${pkg}/core/initializer/onstart_service.dart';
// No-op subscription repository. Run: kasy add revenuecat to activate.
final subscriptionRepositoryProvider = Provider<SubscriptionRepository>(
  (ref) => SubscriptionRepository(),
);

class SubscriptionRepository implements OnStartService {
  SubscriptionRepository({
    dynamic subscriptionApi,
    dynamic inAppSubscriptionApi,
    dynamic prefs,
  });

  @override
  Future<void> init() async {}

  Future<void> initUser(String userId) async {}

  Future<Subscription> get(String userId) async =>
      const Subscription.inactive(hoursBetweenTwoRequests: 24);

  Future<List<SubscriptionProduct>> getOffers({String? offerId}) async => [];

  Future<bool> checkPermission(String permissionToCheck) async => false;

  Future<List<dynamic>?> purchase(SubscriptionProduct product) async => null;

  Future<void> unsubscribe() async {}

  Future<void> restorePurchase() async {}

  Future<void> saveLastAskingDate() async {}

  DateTime? get lastAskingDate => null;
}

class UserCancelledPurchaseException implements Exception {}
`,
    'utf8',
  );

  // 2. No-op maybeshow_premium.dart (used by home_page — must implement MaybeShowWithRef)
  await fs.outputFile(
    path.join(projectDir, 'lib', 'features', 'subscription', 'shared', 'maybeshow_premium.dart'),
    `import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:${pkg}/core/states/components/maybeshow_component.dart';
import 'package:${pkg}/core/states/models/event_model.dart';
// No-op premium paywall trigger. Run: kasy add revenuecat to activate.
class MaybeShowPremiumPage implements MaybeShowWithRef {
  @override
  Future<bool> handle(WidgetRef ref, AppEvent event) async => false;
}
`,
    'utf8',
  );

  // 3. No-op premium_page.dart (used by onboarding_page with args: PremiumPageArgs(redirect:))
  await fs.outputFile(
    path.join(projectDir, 'lib', 'features', 'subscription', 'ui', 'premium_page.dart'),
    `import 'package:flutter/material.dart';
// No-op premium page. Run: kasy add revenuecat to activate.
class PremiumPage extends StatelessWidget {
  const PremiumPage({super.key, this.paywall, this.args});
  final dynamic paywall;
  final PremiumPageArgs? args;
  @override
  Widget build(BuildContext context) => const SizedBox.shrink();
}

class PremiumPageArgs {
  const PremiumPageArgs({this.redirect});
  final String? redirect;
}
`,
    'utf8',
  );

  // 4. No-op premium_page_factory.dart (used by admin_paywalls).
  // Mirror the constants declared in Firebase/.../premium_page_factory.dart so that
  // admin_routes.dart compiles even without revenuecat selected.
  await fs.outputFile(
    path.join(projectDir, 'lib', 'features', 'subscription', 'ui', 'component', 'premium_page_factory.dart'),
    `// No-op paywall factory. Run: kasy add revenuecat to activate.
enum PaywallFactory { basic, basicRow, minimal, withSwitch }
`,
    'utf8',
  );

  // 5. No-op subscription_entity.dart (used by test_utils, fake_subscription_api)
  await fs.outputFile(
    path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'entities', 'subscription_entity.dart'),
    `// No-op subscription entity. Run: kasy add revenuecat to activate.
enum SubscriptionStatus { ACTIVE, PAUSED, EXPIRED, LIFETIME, CANCELLED }

class SubscriptionEntity {
  final String skuId;
  final String? offerId;
  final DateTime? creationDate;
  final DateTime? periodEndDate;
  final SubscriptionStatus status;
  const SubscriptionEntity({
    required this.skuId,
    required this.status,
    this.offerId,
    this.creationDate,
    this.periodEndDate,
  });
}
`,
    'utf8',
  );

  // 6. No-op subscription_api.dart (provider + abstract class used by test_utils and fake)
  await fs.outputFile(
    path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'subscription_api.dart'),
    `import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:${pkg}/features/subscription/api/entities/subscription_entity.dart';
// No-op subscription API. Run: kasy add revenuecat to activate.
final subscriptionApiProvider = Provider<SubscriptionApi>(
  (ref) => _NoOpSubscriptionApi(),
);

abstract class SubscriptionApi {
  Future<SubscriptionEntity?> get(String userId);
}

class _NoOpSubscriptionApi implements SubscriptionApi {
  @override
  Future<SubscriptionEntity?> get(String userId) async => null;
}
`,
    'utf8',
  );

  // 7. No-op inapp_subscription_api.dart (provider + class used by test_utils and fake)
  await fs.outputFile(
    path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'inapp_subscription_api.dart'),
    `import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:${pkg}/core/data/models/subscription.dart';
// No-op in-app subscription API. Run: kasy add revenuecat to activate.
final inAppSubscriptionApiProvider = Provider<RevenueCatPaymentApi>(
  (ref) => RevenueCatPaymentApi(),
);

class RevenueCatPaymentApi {
  Future<void> init() async {}
  Future<void> initUser(String userId) async {}
  Future<void> disconnectUser() async {}
  Future<List<SubscriptionProduct>> getOffers(String? offerId) async => [];
  Future<void> unsubscribe() async {}
  Future<void> restorePurchase() async {}
  Future<List<SubscriptionProduct>> getActiveSubscription() async => [];
  Future<SubscriptionProduct?> getFromProductId(String productId) async => null;
  Future<List<dynamic>> getLastPurchase() async => [];
  Future<void> presentCodeRedemptionSheet() async {}
  Future<List<dynamic>> getPermissions() async => [];
  Future<List<dynamic>?> getEntitlements() async => [];
}
`,
    'utf8',
  );

  // 8. Remove test/features/subscription/ entirely (imports purchases_flutter and RevenueCat types)
  const testSubscriptionDir = path.join(projectDir, 'test', 'features', 'subscription');
  if (await fs.pathExists(testSubscriptionDir)) {
    await fs.remove(testSubscriptionDir);
  }

  // 9. Restore fake_subscription_api.dart with currentFake setter (required by test_utils.dart)
  await fs.outputFile(
    path.join(projectDir, 'test', 'features', 'subscription', 'api', 'fake_subscription_api.dart'),
    `import 'package:${pkg}/features/subscription/api/entities/subscription_entity.dart';
import 'package:${pkg}/features/subscription/api/subscription_api.dart';
// No-op subscription API fake. Run: kasy add revenuecat to activate.
class SubscriptionApiFake implements SubscriptionApi {
  SubscriptionEntity? currentFake;

  @override
  Future<SubscriptionEntity?> get(String userId) async => currentFake;
}
`,
    'utf8',
  );

  // 10. Restore fake_inapp_subscription_api.dart without purchases_flutter dependency
  await fs.outputFile(
    path.join(projectDir, 'test', 'features', 'subscription', 'api', 'fake_inapp_subscription_api.dart'),
    `import 'package:${pkg}/features/subscription/api/inapp_subscription_api.dart';
// No-op in-app subscription API fake. Run: kasy add revenuecat to activate.
class InAppSubscriptionApiFake extends RevenueCatPaymentApi {}
`,
    'utf8',
  );
}

/**
 * Remove DEVELOPMENT_TEAM entries from the generated iOS project.pbxproj.
 * Clients use their own Apple team — leaving the internal team ID would cause
 * signing failures for any account that doesn't belong to AICRUS.
 *
 * @param {string} projectDir
 */
async function removeDevelopmentTeam(projectDir) {
  const pbxprojPath = path.join(projectDir, 'ios', 'Runner.xcodeproj', 'project.pbxproj');
  if (!(await fs.pathExists(pbxprojPath))) return;
  let content = await fs.readFile(pbxprojPath, 'utf8');
  content = content.replace(/^\s*DEVELOPMENT_TEAM = [A-Z0-9]+;\n/gm, '');
  await fs.outputFile(pbxprojPath, content, 'utf8');
}

const RELEASE_DOC_BASES = ['ios-release', 'codemagic-release', 'auth-setup'];
const RELEASE_DOC_LANGS = ['pt', 'en', 'es'];

/**
 * Deliver a single localized release doc per topic (no pt/en/es duplicates).
 * Source templates in the kit repo use `{base}.{lang}.md`; projects receive `{base}.md`.
 *
 * @param {string} projectDir
 * @param {string} [language='en'] - CLI language (en, pt, es)
 */
async function localizeReleaseDocs(projectDir, language = 'en') {
  const lang = RELEASE_DOC_LANGS.includes(language) ? language : 'en';
  const docsDir = path.join(projectDir, 'docs');
  if (!(await fs.pathExists(docsDir))) return;

  for (const base of RELEASE_DOC_BASES) {
    const sourcePath = path.join(docsDir, `${base}.${lang}.md`);
    const targetPath = path.join(docsDir, `${base}.md`);
    if (!(await fs.pathExists(sourcePath))) continue;

    let content = await fs.readFile(sourcePath, 'utf8');
    content = content
      .replace(/ios-release\.(?:pt|en|es)\.md/g, 'ios-release.md')
      .replace(/codemagic-release\.(?:pt|en|es)\.md/g, 'codemagic-release.md')
      .replace(/auth-setup\.(?:pt|en|es)\.md/g, 'auth-setup.md');
    await fs.outputFile(targetPath, content, 'utf8');

    for (const l of RELEASE_DOC_LANGS) {
      const localized = path.join(docsDir, `${base}.${l}.md`);
      if (await fs.pathExists(localized)) await fs.remove(localized);
    }
  }
}

module.exports = {
  BACKENDS,
  buildDartDefines,
  resolveDefaultRcKeys,
  writeRouter,
  writeVsCodeLaunch,
  writeEnvExample,
  writeEnvFileIfMissing,
  writeFirebaserc,
  writeEnvironnementsOverrides,
  writeFeaturesConfig,
  writeKitSetup,
  writeMakefile,
  writeNoOpAnalyticsApi,
  writeNoOpTrackingApi,
  writeMainDart,
  removeModuleDirs,
  removeAndroidWidgetArtifacts,
  removeFacebookSigninFromAuthPages,
  removeAndroidFacebookMetadata,
  writeNoOpAdminHomeWidgets,
  writeNoOpFeatureRequestRepository,
  writeNoOpSubscriptionStubs,
  writeNoOpSentryUsages,
  stripPubspecDeps,
  addPubspecDep,
  removeDevelopmentTeam,
  localizeReleaseDocs,
};
