/**
 * Post-generation steps shared by all backends:
 *   1. flutter pub get
 *   2. dart run slang (translations)
 *   3. dart run build_runner build
 *   4. flutterfire configure (FCM + Remote Config)
 *   5. write google_auth_options.dart (kGoogleWebClientId from google-services.json)
 *
 * @module scaffold/shared/post-build
 */

const { exec } = require('node:child_process');
const { promisify } = require('node:util');
const path = require('node:path');
const fs = require('fs-extra');

const execAsync = promisify(exec);
const MAX_BUFFER = 100 * 1024 * 1024; // 100 MB — build_runner can be verbose

async function run(cmd, cwd, timeout) {
  try {
    const opts = { cwd, maxBuffer: MAX_BUFFER };
    if (timeout) opts.timeout = timeout;
    const { stdout, stderr } = await execAsync(cmd, opts);
    return { ok: true, stdout, stderr };
  } catch (err) {
    return {
      ok: false,
      error: err.message,
      stdout: err.stdout || '',
      stderr: err.stderr || '',
    };
  }
}

async function pubGet(projectDir) {
  return run('flutter pub get', projectDir, 300_000); // 5 min
}

async function slangGenerate(projectDir) {
  return run('dart run slang', projectDir, 120_000); // 2 min
}

async function buildRunner(projectDir) {
  return run('dart run build_runner build --delete-conflicting-outputs', projectDir, 600_000); // 10 min
}

async function flutterfireConfigure(projectDir, firebaseProjectId, options = {}) {
  const { includeWeb = true } = options;
  const platforms = includeWeb ? 'android,ios,web' : 'android,ios';
  const args = [
    `--project=${firebaseProjectId}`,
    '--out lib/firebase_options_dev.dart',
    `--platforms=${platforms}`,
    '--yes',
  ].join(' ');

  // Prefer the binary on PATH; fall back to the pub-cache absolute path for
  // environments where ~/.pub-cache/bin isn't exported to child processes.
  const result = await run(`flutterfire configure ${args}`, projectDir, 300_000);
  if (!result.ok && (result.error?.includes('ENOENT') || result.error?.includes('not found'))) {
    const pubCacheBin = `${process.env.HOME}/.pub-cache/bin/flutterfire`;
    return run(`${pubCacheBin} configure ${args}`, projectDir, 300_000);
  }
  return result;
}

/**
 * Read the Web Client ID (client_type: 3) from google-services.json and write
 * it into lib/google_auth_options.dart so google_sign_in v7 works without any
 * manual step from the client.
 */
async function writeGoogleAuthOptions(projectDir) {
  const googleServicesPath = path.join(
    projectDir,
    'android',
    'app',
    'google-services.json',
  );

  if (!(await fs.pathExists(googleServicesPath))) {
    return { ok: false, error: 'google-services.json not found — skipping google_auth_options.dart' };
  }

  let googleServices;
  try {
    googleServices = await fs.readJson(googleServicesPath);
  } catch (err) {
    return { ok: false, error: `Failed to parse google-services.json: ${err.message}` };
  }

  let webClientId = '';
  for (const client of googleServices.client || []) {
    for (const oauthClient of client.oauth_client || []) {
      if (oauthClient.client_type === 3) {
        webClientId = oauthClient.client_id;
        break;
      }
    }
    if (webClientId) break;
  }

  if (!webClientId) {
    return {
      ok: false,
      error:
        'Web Client ID (client_type: 3) not found in google-services.json. ' +
        'Enable Google Sign-In in Firebase Console → Authentication → Sign-in method → Google.',
    };
  }

  const content =
    `// Google Sign-In Web Client ID (also called "Server Client ID")\n` +
    `//\n` +
    `// ⚠️  Auto-generated by the CLI from android/app/google-services.json.\n` +
    `// Do NOT edit manually. Re-run \`kasy new\` or replace google-services.json to update.\n` +
    `//\n` +
    `// How to get it manually:\n` +
    `//   Firebase Console → Authentication → Sign-in method → Google\n` +
    `//   → Web SDK configuration → Web client ID\n` +
    `// Or from google-services.json: look for "client_type": 3 → "client_id"\n` +
    `const kGoogleWebClientId = '${webClientId}';\n`;

  try {
    await fs.outputFile(
      path.join(projectDir, 'lib', 'google_auth_options.dart'),
      content,
      'utf8',
    );
    return { ok: true };
  } catch (err) {
    return { ok: false, error: `Failed to write google_auth_options.dart: ${err.message}` };
  }
}

/**
 * Find the index of the closing </array> tag that matches the <array> at startPos,
 * properly accounting for nested <array> tags.
 */
function findMatchingArrayEnd(content, startPos) {
  let depth = 0;
  let i = startPos;
  while (i < content.length) {
    if (content.startsWith('<array>', i)) {
      depth++;
      i += '<array>'.length;
    } else if (content.startsWith('</array>', i)) {
      depth--;
      if (depth === 0) return i;
      i += '</array>'.length;
    } else {
      i++;
    }
  }
  return -1;
}

function extractPlistString(content, key) {
  const match = content.match(new RegExp(`<key>${key}<\\/key>\\s*<string>(.*?)<\\/string>`, 's'));
  return match ? match[1].trim() : '';
}

function extractBundleUrlSchemes(infoPlistContent) {
  const schemes = [];
  const schemesBlockRegex = /<key>CFBundleURLSchemes<\/key>\s*<array>([\s\S]*?)<\/array>/g;
  let blockMatch;
  while ((blockMatch = schemesBlockRegex.exec(infoPlistContent)) !== null) {
    const stringRegex = /<string>([^<]+)<\/string>/g;
    let stringMatch;
    while ((stringMatch = stringRegex.exec(blockMatch[1])) !== null) {
      schemes.push(stringMatch[1].trim());
    }
  }
  return schemes;
}

function deriveGoogleIosUrlSchemeFromClientId(iosClientId) {
  const trimmed = (iosClientId || '').trim();
  const prefix = trimmed.replace(/\.apps\.googleusercontent\.com$/, '');
  if (!prefix || prefix === trimmed) return '';
  return `com.googleusercontent.apps.${prefix}`;
}

function googleIosUrlSchemeEntry(reversedClientId) {
  return (
    `\t\t<dict>\n` +
    `\t\t\t<key>CFBundleTypeRole</key>\n` +
    `\t\t\t<string>Editor</string>\n` +
    `\t\t\t<key>CFBundleURLSchemes</key>\n` +
    `\t\t\t<array>\n` +
    `\t\t\t\t<string>${reversedClientId}</string>\n` +
    `\t\t\t</array>\n` +
    `\t\t</dict>\n`
  );
}

function replaceFirstStaleGoogleIosUrlScheme(infoPlistContent, reversedClientId) {
  let replacedScheme = '';
  const updated = infoPlistContent.replace(
    /(<key>CFBundleURLSchemes<\/key>\s*<array>)([\s\S]*?)(<\/array>)/g,
    (full, open, body, close) => {
      if (replacedScheme || body.includes(`<string>${reversedClientId}</string>`)) {
        return full;
      }
      const nextBody = body.replace(
        /<string>(com\.googleusercontent\.apps\.[^<]+)<\/string>/,
        (stringTag, staleScheme) => {
          replacedScheme = staleScheme;
          return `<string>${reversedClientId}</string>`;
        },
      );
      return `${open}${nextBody}${close}`;
    },
  );
  return { content: updated, replacedScheme };
}

function insertGoogleIosUrlScheme(infoPlistContent, reversedClientId) {
  const newEntry = googleIosUrlSchemeEntry(reversedClientId);
  const cfBundleTypesKeyPos = infoPlistContent.indexOf('<key>CFBundleURLTypes</key>');

  if (cfBundleTypesKeyPos === -1) {
    const rootDictEnd = infoPlistContent.lastIndexOf('</dict>');
    if (rootDictEnd === -1) {
      return { ok: false, error: 'Could not locate root </dict> in Info.plist' };
    }
    const block =
      `\t<key>CFBundleURLTypes</key>\n` +
      `\t<array>\n` +
      newEntry +
      `\t</array>\n`;
    return {
      ok: true,
      content: infoPlistContent.slice(0, rootDictEnd) + block + infoPlistContent.slice(rootDictEnd),
    };
  }

  const arrayStartPos = infoPlistContent.indexOf('<array>', cfBundleTypesKeyPos);
  if (arrayStartPos === -1) {
    return { ok: false, error: 'Malformed CFBundleURLTypes in Info.plist' };
  }
  const arrayEndPos = findMatchingArrayEnd(infoPlistContent, arrayStartPos);
  if (arrayEndPos === -1) {
    return { ok: false, error: 'Could not find closing </array> for CFBundleURLTypes' };
  }
  return {
    ok: true,
    content: infoPlistContent.slice(0, arrayEndPos) + newEntry + infoPlistContent.slice(arrayEndPos),
  };
}

function syncGoogleIosUrlSchemeContent(infoPlistContent, reversedClientId) {
  const schemes = extractBundleUrlSchemes(infoPlistContent);
  if (schemes.includes(reversedClientId)) {
    return { ok: true, content: infoPlistContent, changed: false };
  }

  const replacement = replaceFirstStaleGoogleIosUrlScheme(infoPlistContent, reversedClientId);
  if (replacement.replacedScheme) {
    return {
      ok: true,
      content: replacement.content,
      changed: true,
      detail: `updated stale iOS Google URL scheme ${replacement.replacedScheme}`,
    };
  }

  const inserted = insertGoogleIosUrlScheme(infoPlistContent, reversedClientId);
  if (!inserted.ok) return inserted;
  return {
    ok: true,
    content: inserted.content,
    changed: true,
    detail: 'registered iOS Google URL scheme',
  };
}

/**
 * Register the REVERSED_CLIENT_ID from GoogleService-Info.plist as a URL scheme
 * in ios/Runner/Info.plist so that Google Sign-In can redirect back to the app
 * on iOS (ASWebAuthenticationSession callback).
 *
 * Without this, Google Sign-In silently fails on iOS after the Safari modal closes.
 */
async function writeGoogleIosUrlScheme(projectDir) {
  const googleServicePlistPath = path.join(
    projectDir,
    'ios',
    'Runner',
    'GoogleService-Info.plist',
  );
  const infoPlistPath = path.join(projectDir, 'ios', 'Runner', 'Info.plist');

  if (!(await fs.pathExists(googleServicePlistPath))) {
    return { ok: false, error: 'GoogleService-Info.plist not found — skipping iOS URL scheme' };
  }
  if (!(await fs.pathExists(infoPlistPath))) {
    return { ok: false, error: 'ios/Runner/Info.plist not found' };
  }

  let googleServiceContent;
  try {
    googleServiceContent = await fs.readFile(googleServicePlistPath, 'utf8');
  } catch (err) {
    return { ok: false, error: `Failed to read GoogleService-Info.plist: ${err.message}` };
  }

  const reversedClientId = extractPlistString(googleServiceContent, 'REVERSED_CLIENT_ID');
  if (!reversedClientId) {
    return {
      ok: false,
      error:
        'REVERSED_CLIENT_ID not found in GoogleService-Info.plist. ' +
        'Enable Google Sign-In in Firebase Console first.',
    };
  }

  let infoPlistContent;
  try {
    infoPlistContent = await fs.readFile(infoPlistPath, 'utf8');
  } catch (err) {
    return { ok: false, error: `Failed to read Info.plist: ${err.message}` };
  }

  const syncResult = syncGoogleIosUrlSchemeContent(infoPlistContent, reversedClientId);
  if (!syncResult.ok) {
    return { ok: false, error: syncResult.error };
  }

  if (!syncResult.changed) {
    return { ok: true };
  }

  try {
    await fs.writeFile(infoPlistPath, syncResult.content, 'utf8');
    return { ok: true, detail: syncResult.detail };
  } catch (err) {
    return { ok: false, error: `Failed to write Info.plist: ${err.message}` };
  }
}

/**
 * After `flutterfire configure --platforms=...,web`, patch web/firebase-messaging-sw.js
 * with the actual Firebase web config values extracted from firebase_options_dev.dart.
 *
 * This is required because the service worker must be self-contained (it cannot import
 * Dart-generated files) and needs the raw Firebase config values.
 */
async function patchFirebaseServiceWorker(projectDir) {
  const optionsPath = path.join(projectDir, 'lib', 'firebase_options_dev.dart');
  const swPath = path.join(projectDir, 'web', 'firebase-messaging-sw.js');

  if (!(await fs.pathExists(optionsPath))) {
    return { ok: false, error: 'firebase_options_dev.dart not found — skipping service worker patch' };
  }
  if (!(await fs.pathExists(swPath))) {
    return { ok: false, error: 'web/firebase-messaging-sw.js not found — skipping service worker patch' };
  }

  let dartContent;
  try {
    dartContent = await fs.readFile(optionsPath, 'utf8');
  } catch (err) {
    return { ok: false, error: `Failed to read firebase_options_dev.dart: ${err.message}` };
  }

  // Extract the web FirebaseOptions block. flutterfire generates something like:
  //   static const FirebaseOptions web = FirebaseOptions(
  //     apiKey: '...',
  //     appId: '...',
  //     messagingSenderId: '...',
  //     projectId: '...',
  //     authDomain: '...',
  //     storageBucket: '...',
  //     measurementId: '...',
  //   );
  const webBlockMatch = dartContent.match(/static const FirebaseOptions web = FirebaseOptions\(([\s\S]*?)\);/);
  if (!webBlockMatch) {
    return { ok: false, error: 'Could not find web FirebaseOptions block in firebase_options_dev.dart' };
  }

  const webBlock = webBlockMatch[1];

  function extractField(field) {
    const m = webBlock.match(new RegExp(`${field}:\\s*'([^']*)'`));
    return m ? m[1] : '';
  }

  const apiKey           = extractField('apiKey');
  const appId            = extractField('appId');
  const messagingSenderId = extractField('messagingSenderId');
  const projectId        = extractField('projectId');
  const authDomain       = extractField('authDomain');
  const storageBucket    = extractField('storageBucket');
  const measurementId    = extractField('measurementId');

  if (!apiKey || !appId || !messagingSenderId || !projectId) {
    return {
      ok: false,
      error: 'Could not extract required Firebase web config fields from firebase_options_dev.dart',
    };
  }

  let swContent;
  try {
    swContent = await fs.readFile(swPath, 'utf8');
  } catch (err) {
    return { ok: false, error: `Failed to read firebase-messaging-sw.js: ${err.message}` };
  }

  // Replace placeholder values with real ones.
  // The template uses markers like YOUR_FIREBASE_WEB_API_KEY etc.
  swContent = swContent
    .replace(/apiKey:\s*['"][^'"]*['"]/, () => `apiKey: '${apiKey}'`)
    .replace(/appId:\s*['"][^'"]*['"]/, () => `appId: '${appId}'`)
    .replace(/messagingSenderId:\s*['"][^'"]*['"]/, () => `messagingSenderId: '${messagingSenderId}'`)
    .replace(/projectId:\s*['"][^'"]*['"]/, () => `projectId: '${projectId}'`)
    .replace(/authDomain:\s*['"][^'"]*['"]/, () => `authDomain: '${authDomain}'`)
    .replace(/storageBucket:\s*['"][^'"]*['"]/, () => `storageBucket: '${storageBucket}'`);

  if (measurementId) {
    swContent = swContent.replace(/measurementId:\s*['"][^'"]*['"]/, () => `measurementId: '${measurementId}'`);
  }

  try {
    await fs.writeFile(swPath, swContent, 'utf8');
    return { ok: true };
  } catch (err) {
    return { ok: false, error: `Failed to write firebase-messaging-sw.js: ${err.message}` };
  }
}

/**
 * Write lib/google_auth_options.dart for the Supabase backend.
 * Contains both the Web Client ID (serverClientId) and the iOS Client ID.
 *
 * For Firebase this file is auto-generated from google-services.json.
 * For Supabase we write it directly from the credentials collected by the CLI.
 */
async function writeSupabaseGoogleAuthOptions(projectDir, webClientId, iosClientId = '') {
  const content =
    `// Google Sign-In Client IDs for Supabase backend.\n` +
    `//\n` +
    `// ⚠️  Auto-generated by the CLI from the credentials you provided.\n` +
    `// Re-run \`kasy new\` or edit manually to update.\n` +
    `//\n` +
    `// Web Client ID: console.cloud.google.com → Credentials → OAuth 2.0 → Web application\n` +
    `// iOS Client ID: console.cloud.google.com → Credentials → OAuth 2.0 → iOS\n` +
    `\n` +
    `/// Web / Server Client ID — used as serverClientId on Android and for\n` +
    `/// the Supabase provider configuration.\n` +
    `const kGoogleWebClientId = '${webClientId}';\n` +
    `\n` +
    `/// iOS OAuth 2.0 Client ID — required for native Sign-In on iOS.\n` +
    `const kGoogleIosClientId = '${iosClientId}';\n`;

  try {
    await fs.outputFile(
      path.join(projectDir, 'lib', 'google_auth_options.dart'),
      content,
      'utf8',
    );
    return { ok: true };
  } catch (err) {
    return { ok: false, error: `Failed to write google_auth_options.dart: ${err.message}` };
  }
}

/**
 * Derive the REVERSED_CLIENT_ID from a Google OAuth iOS Client ID and register it
 * as a URL scheme in ios/Runner/Info.plist so Google Sign-In can redirect back
 * to the app after authentication.
 *
 * iOS Client ID format:  123456789-xxxx.apps.googleusercontent.com
 * REVERSED_CLIENT_ID:    com.googleusercontent.apps.123456789-xxxx
 */
async function writeGoogleIosUrlSchemeFromClientId(projectDir, iosClientId) {
  if (!iosClientId || !iosClientId.trim()) {
    return { ok: false, error: 'No iOS Client ID provided — skipping iOS URL scheme' };
  }

  const reversedClientId = deriveGoogleIosUrlSchemeFromClientId(iosClientId);
  if (!reversedClientId) {
    return { ok: false, error: `Invalid iOS Client ID format: ${iosClientId}` };
  }

  const infoPlistPath = path.join(projectDir, 'ios', 'Runner', 'Info.plist');
  if (!(await fs.pathExists(infoPlistPath))) {
    return { ok: false, error: 'ios/Runner/Info.plist not found' };
  }

  let infoPlistContent;
  try {
    infoPlistContent = await fs.readFile(infoPlistPath, 'utf8');
  } catch (err) {
    return { ok: false, error: `Failed to read Info.plist: ${err.message}` };
  }

  const syncResult = syncGoogleIosUrlSchemeContent(infoPlistContent, reversedClientId);
  if (!syncResult.ok) {
    return { ok: false, error: syncResult.error };
  }

  if (!syncResult.changed) {
    return { ok: true };
  }

  try {
    await fs.writeFile(infoPlistPath, syncResult.content, 'utf8');
    return { ok: true, detail: syncResult.detail };
  } catch (err) {
    return { ok: false, error: `Failed to write Info.plist: ${err.message}` };
  }
}

async function validateGoogleIosUrlScheme(projectDir) {
  const googleServicePlistPath = path.join(
    projectDir,
    'ios',
    'Runner',
    'GoogleService-Info.plist',
  );
  const infoPlistPath = path.join(projectDir, 'ios', 'Runner', 'Info.plist');

  if (!(await fs.pathExists(googleServicePlistPath))) {
    return { ok: true, skipped: true, reason: 'missing_google_service_plist' };
  }
  if (!(await fs.pathExists(infoPlistPath))) {
    return { ok: false, issue: 'missing_info_plist', error: 'ios/Runner/Info.plist not found' };
  }

  let googleServiceContent;
  let infoPlistContent;
  try {
    googleServiceContent = await fs.readFile(googleServicePlistPath, 'utf8');
  } catch (err) {
    return { ok: false, issue: 'read_google_service_failed', error: `Failed to read GoogleService-Info.plist: ${err.message}` };
  }
  try {
    infoPlistContent = await fs.readFile(infoPlistPath, 'utf8');
  } catch (err) {
    return { ok: false, issue: 'read_info_plist_failed', error: `Failed to read Info.plist: ${err.message}` };
  }

  const reversedClientId = extractPlistString(googleServiceContent, 'REVERSED_CLIENT_ID');
  if (!reversedClientId) {
    return {
      ok: false,
      issue: 'missing_reversed_client_id',
      error:
        'REVERSED_CLIENT_ID not found in ios/Runner/GoogleService-Info.plist. ' +
        'Enable Google Sign-In in Firebase Console and re-run flutterfire configure.',
    };
  }

  const schemes = extractBundleUrlSchemes(infoPlistContent);
  if (!schemes.includes(reversedClientId)) {
    return {
      ok: false,
      issue: 'missing_google_ios_url_scheme',
      reversedClientId,
      schemes,
      error:
        `iOS Google Sign-In URL scheme mismatch. Expected ${reversedClientId} from ` +
        'ios/Runner/GoogleService-Info.plist, but ios/Runner/Info.plist does not declare it under CFBundleURLSchemes. ' +
        'Run kasy ios clean or re-run project setup so the CLI can sync the plist before building.',
    };
  }

  return { ok: true, reversedClientId, schemes };
}

/**
 * Validate that the Apple Sign-In entitlement is present in Runner.entitlements.
 * Required for native iOS Apple Sign-In to work.
 */
async function validateAppleSignInEntitlement(projectDir) {
  const entitlementsPath = path.join(projectDir, 'ios', 'Runner', 'Runner.entitlements');
  if (!(await fs.pathExists(entitlementsPath))) {
    return { ok: false, issue: 'missing_entitlements_file', error: 'ios/Runner/Runner.entitlements not found' };
  }
  let content;
  try {
    content = await fs.readFile(entitlementsPath, 'utf8');
  } catch (err) {
    return { ok: false, issue: 'read_entitlements_failed', error: `Failed to read Runner.entitlements: ${err.message}` };
  }
  if (!content.includes('com.apple.developer.applesignin')) {
    return {
      ok: false,
      issue: 'missing_apple_signin_entitlement',
      error:
        'com.apple.developer.applesignin entitlement not found in ios/Runner/Runner.entitlements. ' +
        'Open Xcode → Runner target → Signing & Capabilities → + Capability → Sign In with Apple.',
    };
  }
  return { ok: true };
}

/**
 * Validate that Facebook placeholders in Info.plist have been replaced with real values.
 */
async function validateFacebookInfoPlist(projectDir) {
  const infoPlistPath = path.join(projectDir, 'ios', 'Runner', 'Info.plist');
  if (!(await fs.pathExists(infoPlistPath))) {
    return { ok: true, skipped: true, reason: 'missing_info_plist' };
  }
  let content;
  try {
    content = await fs.readFile(infoPlistPath, 'utf8');
  } catch (err) {
    return { ok: false, issue: 'read_info_plist_failed', error: `Failed to read Info.plist: ${err.message}` };
  }
  if (!content.includes('<key>FacebookAppID</key>')) {
    return { ok: true, skipped: true, reason: 'no_facebook_key' };
  }
  const appIdMatch = content.match(/<key>FacebookAppID<\/key>\s*<string>([^<]+)<\/string>/);
  const tokenMatch = content.match(/<key>FacebookClientToken<\/key>\s*<string>([^<]+)<\/string>/);
  const appId = appIdMatch ? appIdMatch[1].trim() : '';
  const token = tokenMatch ? tokenMatch[1].trim() : '';
  const isPlaceholderId = !appId || /^0+$/.test(appId);
  const isPlaceholderToken = !token || /^0+$/.test(token);
  if (isPlaceholderId || isPlaceholderToken) {
    return {
      ok: false,
      issue: 'facebook_placeholders',
      error:
        'FacebookAppID or FacebookClientToken in ios/Runner/Info.plist still has placeholder values. ' +
        'Replace them with real values from Meta for Developers → Settings → Basic.',
    };
  }
  return { ok: true };
}

async function validateFacebookAndroidStrings(projectDir) {
  const stringsPath = path.join(projectDir, 'android', 'app', 'src', 'main', 'res', 'values', 'strings.xml');
  if (!(await fs.pathExists(stringsPath))) {
    return { ok: true, skipped: true, reason: 'missing_strings_xml' };
  }
  let content;
  try {
    content = await fs.readFile(stringsPath, 'utf8');
  } catch (err) {
    return { ok: false, issue: 'read_strings_xml_failed', error: `Failed to read strings.xml: ${err.message}` };
  }
  if (!content.includes('facebook_app_id')) {
    return { ok: true, skipped: true, reason: 'no_facebook_key' };
  }
  const appIdMatch = content.match(/name="facebook_app_id"[^>]*>([^<]+)</);
  const tokenMatch = content.match(/name="facebook_client_token"[^>]*>([^<]+)</);
  const appId = appIdMatch ? appIdMatch[1].trim() : '';
  const token = tokenMatch ? tokenMatch[1].trim() : '';
  const isPlaceholderId = !appId || /^0+$/.test(appId);
  const isPlaceholderToken = !token || /^0+$/.test(token);
  if (isPlaceholderId || isPlaceholderToken) {
    return {
      ok: false,
      issue: 'facebook_android_placeholders',
      error:
        'facebook_app_id or facebook_client_token in android/app/src/main/res/values/strings.xml still has placeholder values. ' +
        'Replace them with real values from Meta for Developers → Settings → Basic.',
    };
  }
  return { ok: true };
}

/**
 * Read a project `.env` file into a plain object. Returns `{}` if the file is
 * missing. Supports KEY=VALUE lines; ignores comments and blank lines.
 */
async function readDotenv(projectDir) {
  const envPath = path.join(projectDir, '.env');
  if (!(await fs.pathExists(envPath))) return {};
  let content;
  try {
    content = await fs.readFile(envPath, 'utf8');
  } catch {
    return {};
  }
  const env = {};
  for (const rawLine of content.split('\n')) {
    const line = rawLine.trim();
    if (!line || line.startsWith('#')) continue;
    const eq = line.indexOf('=');
    if (eq === -1) continue;
    const key = line.slice(0, eq).trim();
    let value = line.slice(eq + 1).trim();
    if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
      value = value.slice(1, -1);
    }
    if (key) env[key] = value;
  }
  return env;
}

/**
 * Validate RevenueCat keys. The new `.env` is the source of truth:
 *   RC_TEST_KEY (test_xxx — simulator/emulator)
 *   RC_IOS_PROD_KEY (appl_xxx — physical iOS)
 *   RC_ANDROID_PROD_KEY (goog_xxx — physical Android)
 *
 * Legacy fallback: if none of the new vars are set we read the old
 * RC_ANDROID_API_KEY/RC_IOS_API_KEY from `.env`, and finally the Makefile
 * (oldest projects). Returns flags `doctor.js` uses to render granular hints.
 */
async function validateRevenueCat(projectDir, config = {}) {
  const env = await readDotenv(projectDir);

  // Webhook URL (only Supabase exposes it directly; Firebase points to Console).
  let webhookUrl = null;
  if (config.backendProvider === 'supabase' && config.supabaseProjectId) {
    webhookUrl = `https://${config.supabaseProjectId}.supabase.co/functions/v1/revenuecat-webhook`;
  }

  const testKey = (env.RC_TEST_KEY || '').trim();
  const iosProdKey = (env.RC_IOS_PROD_KEY || '').trim();
  const androidProdKey = (env.RC_ANDROID_PROD_KEY || '').trim();
  const hasNewKeys = !!(testKey || iosProdKey || androidProdKey);

  // Legacy single-key path (for projects generated before the test/prod split).
  if (!hasNewKeys) {
    let iosKey = (env.RC_IOS_API_KEY || '').trim();
    let androidKey = (env.RC_ANDROID_API_KEY || '').trim();

    // Last resort: read from Makefile (oldest projects).
    if (!iosKey && !androidKey) {
      const makefilePath = path.join(projectDir, 'Makefile');
      if (await fs.pathExists(makefilePath)) {
        try {
          const content = await fs.readFile(makefilePath, 'utf8');
          const activeLines = content.split('\n').filter((l) => !/^\s*#/.test(l)).join('\n');
          const iosMatch = activeLines.match(/RC_IOS_API_KEY=([^\s\\]+)/);
          const androidMatch = activeLines.match(/RC_ANDROID_API_KEY=([^\s\\]+)/);
          iosKey = iosMatch ? iosMatch[1].trim() : '';
          androidKey = androidMatch ? androidMatch[1].trim() : '';
        } catch { /* ignore */ }
      }
    }

    const iosEmpty = !iosKey || iosKey === 'xxx';
    const androidEmpty = !androidKey || androidKey === 'xxx';
    const iosTest = iosKey.startsWith('test_');
    const androidTest = androidKey.startsWith('test_');

    return {
      ok: !iosEmpty && !androidEmpty,
      legacy: true,
      iosKey,
      androidKey,
      iosEmpty,
      androidEmpty,
      bothTest: iosTest && androidTest,
      webhookUrl,
    };
  }

  // New 3-key scheme.
  const testOk = !!testKey && /^test_/.test(testKey);
  const testBadPrefix = !!testKey && !/^test_/.test(testKey);
  const iosProdOk = !!iosProdKey && /^appl_/.test(iosProdKey);
  const iosProdBadPrefix = !!iosProdKey && !/^appl_/.test(iosProdKey);
  const androidProdOk = !!androidProdKey && /^goog_/.test(androidProdKey);
  const androidProdBadPrefix = !!androidProdKey && !/^goog_/.test(androidProdKey);

  // OK if user can run at least one device flow end-to-end. Test alone is fine
  // for development; prod-only configurations also work but skip the simulator.
  const anyOk = testOk || iosProdOk || androidProdOk;

  return {
    ok: anyOk,
    legacy: false,
    testKey,
    iosProdKey,
    androidProdKey,
    testOk,
    testBadPrefix,
    iosProdOk,
    iosProdBadPrefix,
    androidProdOk,
    androidProdBadPrefix,
    testMissing: !testKey,
    iosProdMissing: !iosProdKey,
    androidProdMissing: !androidProdKey,
    onlyTest: testOk && !iosProdOk && !androidProdOk,
    webhookUrl,
  };
}

/**
 * Read Google credentials from files written by flutterfire configure:
 *   - Web Client ID  from android/app/google-services.json (client_type: 3)
 *   - iOS Client ID  from ios/Runner/GoogleService-Info.plist (CLIENT_ID)
 *
 * Returns { webClientId, iosClientId } — empty string if not found.
 */
async function readSupabaseGoogleCredentials(projectDir) {
  let webClientId = '';
  let iosClientId = '';

  // 1. Web Client ID from google-services.json
  try {
    const googleServicesPath = path.join(projectDir, 'android', 'app', 'google-services.json');
    if (await fs.pathExists(googleServicesPath)) {
      const gs = await fs.readJson(googleServicesPath);
      for (const client of gs.client || []) {
        for (const oc of client.oauth_client || []) {
          if (oc.client_type === 3) {
            webClientId = oc.client_id;
            break;
          }
        }
        if (webClientId) break;
      }
    }
  } catch (_) {}

  // 2. iOS Client ID from GoogleService-Info.plist
  try {
    const plistPath = path.join(projectDir, 'ios', 'Runner', 'GoogleService-Info.plist');
    if (await fs.pathExists(plistPath)) {
      const content = await fs.readFile(plistPath, 'utf8');
      const m = content.match(/<key>CLIENT_ID<\/key>\s*<string>([^<]+)<\/string>/);
      if (m) iosClientId = m[1].trim();
    }
  } catch (_) {}

  return { webClientId, iosClientId };
}

/**
 * Attempt to retrieve the Google Sign-In client secret from the Firebase project
 * via Identity Toolkit Admin API using the current gcloud access token.
 *
 * Returns the clientSecret string, or empty string if unavailable.
 */
async function getGoogleClientSecretViaGcloud(firebaseProjectId) {
  if (!firebaseProjectId) return '';
  try {
    const tokenResult = await run('gcloud auth print-access-token', process.cwd());
    if (!tokenResult.ok || !tokenResult.stdout.trim()) return '';
    const token = tokenResult.stdout.trim();

    const curlCmd = [
      'curl -sf',
      `-H "Authorization: Bearer ${token}"`,
      `-H "x-goog-user-project: ${firebaseProjectId}"`,
      `"https://identitytoolkit.googleapis.com/admin/v2/projects/${firebaseProjectId}/defaultSupportedIdpConfigs/google.com"`,
    ].join(' ');

    const result = await run(curlCmd, process.cwd());
    if (!result.ok || !result.stdout.trim()) return '';

    const data = JSON.parse(result.stdout.trim());
    return data.clientSecret || '';
  } catch (_) {
    return '';
  }
}

module.exports = {
  pubGet,
  slangGenerate,
  buildRunner,
  flutterfireConfigure,
  writeGoogleAuthOptions,
  writeGoogleIosUrlScheme,
  writeSupabaseGoogleAuthOptions,
  writeGoogleIosUrlSchemeFromClientId,
  validateGoogleIosUrlScheme,
  validateAppleSignInEntitlement,
  validateFacebookInfoPlist,
  validateFacebookAndroidStrings,
  validateRevenueCat,
  patchFirebaseServiceWorker,
  readSupabaseGoogleCredentials,
  getGoogleClientSecretViaGcloud,
  // Exported for focused unit tests; not part of the public CLI surface.
  _private: {
    deriveGoogleIosUrlSchemeFromClientId,
    extractBundleUrlSchemes,
    syncGoogleIosUrlSchemeContent,
  },
};
