"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateApp = generateApp;
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const execa_1 = require("execa");
async function generateApp(options) {
    const { platform, projectName, auth, apiClient, absoluteImports, state, dataFetching, validation, storage, typescript, apiClientType, husky, } = options;
    const templateRoot = await resolveTemplateRoot();
    const templateFolder = platform === "Expo" ? "expo-base" : "cli-base";
    const templatePath = path_1.default.join(templateRoot, templateFolder);
    const targetPath = path_1.default.join(process.cwd(), projectName);
    console.log("📂 Creating project...");
    await fs_extra_1.default.copy(templatePath, targetPath, {
        filter: (src) => shouldCopyPath(src, templatePath),
    });
    await replaceProjectName(targetPath, projectName);
    if (platform === "React Native CLI") {
        await configureCliNativeProjectNames(targetPath, projectName);
    }
    await createStandardStructure(targetPath, platform, state);
    if (auth) {
        const authFolder = state === "Redux Toolkit"
            ? "auth-redux"
            : state === "Zustand"
                ? "auth-zustand"
                : "auth-context";
        await copyOptionalModule(authFolder, targetPath);
        if (platform === "Expo") {
            await writeExpoRouterAuthRoutes(targetPath, state, dataFetching);
        }
        else {
            await setAuthTabsByPlatform(targetPath, platform);
            await writeAuthAppShell(targetPath, state, dataFetching);
        }
    }
    if (apiClient) {
        await copyOptionalModule("apiClient", targetPath);
        await configureApiClientByPlatform(targetPath, platform);
    }
    // Only copy standalone state modules when auth is disabled
    // (auth modules already include their own store setup)
    if (state === "Redux Toolkit" && !auth)
        await copyOptionalModule("redux", targetPath);
    if (state === "Zustand" && !auth)
        await copyOptionalModule("zustand", targetPath);
    if (dataFetching === "React Query")
        await copyOptionalModule("react-query", targetPath);
    if (dataFetching === "SWR")
        await copyOptionalModule("swr", targetPath);
    if (storage === "MMKV")
        await copyOptionalModule("mmkv", targetPath);
    if (validation.includes("Formik"))
        await copyOptionalModule("formik", targetPath);
    if (validation.includes("React Hook Form"))
        await copyOptionalModule("react-hook-form", targetPath);
    await configureStateAndAuthDependencies(targetPath, {
        platform,
        state,
        auth,
        storage,
    });
    await configureAbsoluteImports(targetPath, platform, absoluteImports);
    await configureDataFetching(targetPath, dataFetching);
    await configureApiClientTransport(targetPath, apiClient, apiClientType);
    await syncApiIndex(targetPath);
    await configureValidation(targetPath, validation);
    await configureTheme(targetPath);
    await configureErrorBoundary(targetPath);
    await configureTesting(targetPath);
    await configureNavigationTypes(targetPath, { platform, auth, state });
    await configureEnv(targetPath, platform);
    await stampVersion(targetPath);
    if (options.ci)
        await configureCi(targetPath);
    if (husky)
        await configureHusky(targetPath);
    if (typescript) {
        // Rename App.js -> App.tsx for projects generated without a .tsx entry point.
        // Both cli-base and expo-base templates already ship App.tsx and correct
        // tsconfigs (extending @react-native/typescript-config / expo/tsconfig.base),
        // so we must NOT overwrite those configs here.
        const appJs = path_1.default.join(targetPath, "App.js");
        const appTsx = path_1.default.join(targetPath, "App.tsx");
        if (await fs_extra_1.default.pathExists(appJs)) {
            await fs_extra_1.default.rename(appJs, appTsx);
        }
    }
    await installDependencies(targetPath);
    console.log("✅ Project created successfully!");
}
async function copyOptionalModule(moduleName, targetPath) {
    const templateRoot = await resolveTemplateRoot();
    const modulePath = path_1.default.join(templateRoot, "optional", moduleName);
    if (await fs_extra_1.default.pathExists(modulePath)) {
        await fs_extra_1.default.copy(modulePath, targetPath, { overwrite: true });
        console.log(`➕ Added optional module: ${moduleName}`);
    }
}
async function replaceProjectName(dir, projectName) {
    const files = await fs_extra_1.default.readdir(dir);
    for (const file of files) {
        const filePath = path_1.default.join(dir, file);
        const stat = await fs_extra_1.default.stat(filePath);
        if (stat.isDirectory()) {
            await replaceProjectName(filePath, projectName);
        }
        else {
            if (!isTextFile(filePath))
                continue;
            const content = await fs_extra_1.default.readFile(filePath, "utf8");
            const updated = content.replace(/{{projectName}}/g, projectName);
            await fs_extra_1.default.writeFile(filePath, updated);
        }
    }
}
function toNativeAppName(projectName) {
    const cleaned = projectName
        .replace(/[^a-zA-Z0-9]+/g, " ")
        .trim()
        .split(/\s+/)
        .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
        .join("");
    const fallback = cleaned || "App";
    return /^\d/.test(fallback) ? `App${fallback}` : fallback;
}
function toAndroidPackageSegment(projectName) {
    const cleaned = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "");
    const fallback = cleaned || "app";
    return /^\d/.test(fallback) ? `app${fallback}` : fallback;
}
async function replaceInTextFiles(dir, replacements) {
    const files = await fs_extra_1.default.readdir(dir);
    for (const file of files) {
        const filePath = path_1.default.join(dir, file);
        const stat = await fs_extra_1.default.stat(filePath);
        if (stat.isDirectory()) {
            await replaceInTextFiles(filePath, replacements);
            continue;
        }
        if (!isTextFile(filePath))
            continue;
        let content = await fs_extra_1.default.readFile(filePath, "utf8");
        for (const [pattern, replacement] of replacements) {
            content = content.replace(pattern, replacement);
        }
        await fs_extra_1.default.writeFile(filePath, content, "utf8");
    }
}
async function renameIfExists(fromPath, toPath) {
    if (!(await fs_extra_1.default.pathExists(fromPath)))
        return;
    await fs_extra_1.default.move(fromPath, toPath, { overwrite: true });
}
async function configureCliNativeProjectNames(targetPath, projectName) {
    const nativeAppName = toNativeAppName(projectName);
    const androidPackageSegment = toAndroidPackageSegment(projectName);
    await replaceInTextFiles(targetPath, [
        [/BaseApp/g, nativeAppName],
        [/com\.baseapp/g, `com.${androidPackageSegment}`],
    ]);
    const iosPath = path_1.default.join(targetPath, "ios");
    const androidJavaRoot = path_1.default.join(targetPath, "android", "app", "src", "main", "java");
    await renameIfExists(path_1.default.join(iosPath, "BaseApp"), path_1.default.join(iosPath, nativeAppName));
    await renameIfExists(path_1.default.join(iosPath, "BaseApp.xcodeproj"), path_1.default.join(iosPath, `${nativeAppName}.xcodeproj`));
    await renameIfExists(path_1.default.join(iosPath, "BaseApp.xcworkspace"), path_1.default.join(iosPath, `${nativeAppName}.xcworkspace`));
    await renameIfExists(path_1.default.join(iosPath, `${nativeAppName}.xcodeproj`, "xcshareddata", "xcschemes", "BaseApp.xcscheme"), path_1.default.join(iosPath, `${nativeAppName}.xcodeproj`, "xcshareddata", "xcschemes", `${nativeAppName}.xcscheme`));
    await renameIfExists(path_1.default.join(androidJavaRoot, "com", "baseapp"), path_1.default.join(androidJavaRoot, "com", androidPackageSegment));
}
async function installDependencies(targetPath) {
    console.log("📦 Installing dependencies...");
    try {
        await (0, execa_1.execa)("npm", ["install"], { cwd: targetPath, stdio: "inherit" });
        console.log("✅ Dependencies installed!");
    }
    catch {
        console.log("⚠️ Peer dep conflict detected, retrying with --legacy-peer-deps...");
        try {
            await (0, execa_1.execa)("npm", ["install", "--legacy-peer-deps"], {
                cwd: targetPath,
                stdio: "inherit",
            });
            console.log("✅ Dependencies installed!");
        }
        catch {
            console.log("⚠️ Failed to install dependencies. Run 'npm install --legacy-peer-deps' manually.");
        }
    }
}
async function setAuthTabsByPlatform(targetPath, platform) {
    const navigationPath = path_1.default.join(targetPath, "navigation");
    const targetTabs = path_1.default.join(navigationPath, "BottomTabs.tsx");
    const tabSourceImport = platform === "Expo" ? "./expo/BottomTabs" : "./cli/BottomTabs";
    if (!(await fs_extra_1.default.pathExists(navigationPath)))
        return;
    await fs_extra_1.default.writeFile(targetTabs, `export { default } from "${tabSourceImport}";\n`, "utf8");
}
async function configureApiClientByPlatform(targetPath, platform) {
    const configPath = path_1.default.join(targetPath, "config");
    const envSelectorPath = path_1.default.join(configPath, "env.ts");
    const envImport = platform === "Expo" ? "./env.expo" : "./env.cli";
    if (await fs_extra_1.default.pathExists(configPath)) {
        await fs_extra_1.default.writeFile(envSelectorPath, `export { API_BASE_URL } from "${envImport}";\n`, "utf8");
    }
    await ensureApiEnvFiles(targetPath, platform);
    if (platform === "React Native CLI") {
        await ensureCliApiEnvSupport(targetPath);
    }
}
async function ensureApiEnvFiles(targetPath, platform) {
    const key = platform === "Expo" ? "EXPO_PUBLIC_API_URL" : "API_BASE_URL";
    const envLine = `${key}=https://example.com/api\n`;
    const envPath = path_1.default.join(targetPath, ".env");
    const envExamplePath = path_1.default.join(targetPath, ".env.example");
    if (!(await fs_extra_1.default.pathExists(envPath))) {
        await fs_extra_1.default.writeFile(envPath, envLine, "utf8");
    }
    if (!(await fs_extra_1.default.pathExists(envExamplePath))) {
        await fs_extra_1.default.writeFile(envExamplePath, envLine, "utf8");
    }
}
async function ensureCliApiEnvSupport(targetPath) {
    await ensureDependencies(targetPath, {
        devDependencies: {
            "react-native-dotenv": "^3.4.11",
        },
    });
}
async function createStandardStructure(targetPath, platform, state) {
    const commonDirectories = [
        "assets",
        "assets/icons",
        "assets/images",
        "assets/fonts",
        "components",
        "hooks",
        "utils",
        "services",
        "api",
        "config",
        "theme",
        "providers",
        "types",
    ];
    // Only scaffold a context/ dir when Context API is the chosen state solution
    if (state === "Context API" || state === "None") {
        commonDirectories.push("context");
    }
    const cliOnlyDirectories = ["navigation", "screens"];
    const directories = platform === "Expo"
        ? commonDirectories
        : [...commonDirectories, ...cliOnlyDirectories];
    for (const directory of directories) {
        await fs_extra_1.default.ensureDir(path_1.default.join(targetPath, directory));
    }
}
async function configureAbsoluteImports(targetPath, platform, absoluteImports) {
    await configureTsconfigAlias(targetPath, absoluteImports);
    if (platform === "React Native CLI") {
        if (absoluteImports) {
            await ensureDependencies(targetPath, {
                devDependencies: {
                    "babel-plugin-module-resolver": "^5.0.2",
                },
            });
        }
        await writeCliBabelConfig(targetPath, {
            useDotenv: await usesDotenv(targetPath),
            useAbsoluteImports: absoluteImports,
        });
    }
}
async function configureDataFetching(targetPath, dataFetching) {
    if (dataFetching === "React Query") {
        await ensureDependencies(targetPath, {
            dependencies: {
                "@tanstack/react-query": "^5.90.5",
            },
        });
        // queryClient.ts is provided by the react-query optional module template
        // writeIfMissing ensures we don't overwrite it if already copied
        await writeIfMissing(path_1.default.join(targetPath, "services/queryClient.ts"), `import { QueryClient } from "@tanstack/react-query";

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,
      staleTime: 1000 * 60 * 5,
      gcTime: 1000 * 60 * 10,
    },
    mutations: {
      retry: 0,
    },
  },
});
`);
    }
    if (dataFetching === "SWR") {
        await ensureDependencies(targetPath, {
            dependencies: {
                swr: "^2.3.6",
            },
        });
        // SWRProvider is provided by the swr optional module template
        // Add a fallback if template copy didn't work
        await writeIfMissing(path_1.default.join(targetPath, "providers/SWRProvider.tsx"), `import React from "react";
import { SWRConfig } from "swr";

type Props = { children: React.ReactNode };

export function SWRProvider({ children }: Props) {
  return (
    <SWRConfig
      value={{
        revalidateOnFocus: true,
        revalidateOnReconnect: true,
        shouldRetryOnError: true,
        errorRetryCount: 3,
      }}
    >
      {children}
    </SWRConfig>
  );
}
`);
    }
}
async function configureApiClientTransport(targetPath, apiClient, apiClientType) {
    if (apiClient && apiClientType === "Axios") {
        await ensureDependencies(targetPath, {
            dependencies: {
                axios: "^1.12.2",
            },
        });
        await fs_extra_1.default.writeFile(path_1.default.join(targetPath, "api/client.ts"), `import axios, {
  AxiosError,
  AxiosRequestConfig,
  AxiosResponse,
  InternalAxiosRequestConfig,
} from "axios";
import { API_BASE_URL } from "../config/env";

export class ApiError extends Error {
  status: number;
  data: unknown;

  constructor(status: number, message: string, data: unknown) {
    super(message);
    this.name = "ApiError";
    this.status = status;
    this.data = data;
  }
}

type RequestOptions = {
  headers?: Record<string, string>;
  query?: Record<string, string | number | boolean | undefined>;
  token?: string | null;
  signal?: AbortSignal;
};

const client = axios.create({
  baseURL: API_BASE_URL,
  timeout: 15000,
});

let authTokenGetter: (() => string | null | undefined) | null = null;

export function setAuthTokenGetter(getter: (() => string | null | undefined) | null) {
  authTokenGetter = getter;
}

function resolveErrorMessage(data: unknown, status: number) {
  if (
    typeof data === "object" &&
    data !== null &&
    "message" in data &&
    typeof (data as { message: unknown }).message === "string"
  ) {
    return (data as { message: string }).message;
  }
  return \`Request failed with status \${status}\`;
}

export function addRequestInterceptor(
  interceptor: (
    config: InternalAxiosRequestConfig,
  ) =>
    | InternalAxiosRequestConfig
    | Promise<InternalAxiosRequestConfig>,
) {
  return client.interceptors.request.use(interceptor);
}

export function addResponseInterceptor(
  onFulfilled?: (
    response: AxiosResponse,
  ) => AxiosResponse | Promise<AxiosResponse>,
  onRejected?: (error: AxiosError) => unknown,
) {
  return client.interceptors.response.use(onFulfilled, onRejected);
}

addRequestInterceptor((config) => {
  const token = authTokenGetter?.();
  if (token) {
    config.headers.Authorization = \`Bearer \${token}\`;
  }
  return config;
});

addResponseInterceptor(
  (response) => response,
  (error) => {
    if (axios.isAxiosError(error)) {
      const status = error.response?.status ?? 500;
      const data = error.response?.data ?? null;
      return Promise.reject(new ApiError(status, resolveErrorMessage(data, status), data));
    }
    return Promise.reject(error);
  },
);

async function request<T>(
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
  path: string,
  body?: unknown,
  options: RequestOptions = {},
): Promise<T> {
  const { headers = {}, query, token, signal } = options;
  const requestConfig: AxiosRequestConfig = {
    method,
    url: path,
    data: body,
    params: query,
    signal,
    headers: {
      ...(token ? { Authorization: \`Bearer \${token}\` } : {}),
      ...headers,
    },
  };
  const response = await client.request<T>(requestConfig);
  return response.data;
}

export const apiClient = {
  get: <T>(path: string, options?: RequestOptions) =>
    request<T>("GET", path, undefined, options),
  post: <T>(path: string, body?: unknown, options?: RequestOptions) =>
    request<T>("POST", path, body, options),
  put: <T>(path: string, body?: unknown, options?: RequestOptions) =>
    request<T>("PUT", path, body, options),
  patch: <T>(path: string, body?: unknown, options?: RequestOptions) =>
    request<T>("PATCH", path, body, options),
  delete: <T>(path: string, options?: RequestOptions) =>
    request<T>("DELETE", path, undefined, options),
};
`, "utf8");
        await fs_extra_1.default.remove(path_1.default.join(targetPath, "api/client.axios.ts"));
        await fs_extra_1.default.remove(path_1.default.join(targetPath, "api/axiosClient.ts"));
    }
}
async function syncApiIndex(targetPath) {
    const apiDir = path_1.default.join(targetPath, "api");
    if (!(await fs_extra_1.default.pathExists(apiDir)))
        return;
    const exports = [];
    if (await fs_extra_1.default.pathExists(path_1.default.join(apiDir, "client.ts"))) {
        exports.push('export * from "./client";');
    }
    if (await fs_extra_1.default.pathExists(path_1.default.join(apiDir, "endpoints/auth.ts"))) {
        exports.push('export * from "./endpoints/auth";');
    }
    else if (await fs_extra_1.default.pathExists(path_1.default.join(apiDir, "authApi.ts"))) {
        exports.push('export * from "./authApi";');
    }
    if (exports.length === 0)
        return;
    await fs_extra_1.default.writeFile(path_1.default.join(apiDir, "index.ts"), `${exports.join("\n")}\n`, "utf8");
}
async function configureValidation(targetPath, validation) {
    const dependencies = {};
    if (validation.includes("Formik"))
        dependencies.formik = "^2.4.6";
    if (validation.includes("React Hook Form"))
        dependencies["react-hook-form"] = "^7.62.0";
    if (validation.includes("Yup"))
        dependencies.yup = "^1.7.0";
    if (Object.keys(dependencies).length > 0) {
        await ensureDependencies(targetPath, { dependencies });
    }
}
async function configureStateAndAuthDependencies(targetPath, options) {
    const dependencies = {};
    if (options.auth && options.platform === "React Native CLI") {
        // Full navigation stack required for auth flow
        dependencies["@react-navigation/native"] = "^7.1.8";
        dependencies["@react-navigation/native-stack"] = "^7.3.29";
        dependencies["@react-navigation/bottom-tabs"] = "^7.4.0";
        dependencies["react-native-screens"] = "^4.16.0";
        dependencies["react-native-gesture-handler"] = "^2.28.0";
        dependencies["react-native-reanimated"] = "^3.17.4";
    }
    // Expo with auth uses expo-router tabs which require reanimated
    if (options.auth && options.platform === "Expo") {
        dependencies["react-native-reanimated"] = "^3.17.4";
    }
    // AsyncStorage: needed when explicitly selected OR when auth is enabled
    // (auth modules use AsyncStorage for welcome screen persistence)
    if (options.storage === "AsyncStorage" || options.auth) {
        dependencies["@react-native-async-storage/async-storage"] = "^2.2.0";
    }
    // MMKV: used for token storage when selected
    if (options.storage === "MMKV") {
        dependencies["react-native-mmkv"] = "^3.0.0";
    }
    if (options.state === "Redux Toolkit") {
        dependencies["@reduxjs/toolkit"] = "^2.9.0";
        dependencies["react-redux"] = "^9.2.0";
    }
    if (options.state === "Zustand") {
        dependencies.zustand = "^5.0.8";
    }
    if (Object.keys(dependencies).length > 0) {
        await ensureDependencies(targetPath, { dependencies });
    }
}
async function writeExpoRouterAuthRoutes(targetPath, state, dataFetching = "None") {
    const appDir = path_1.default.join(targetPath, "app");
    const authDir = path_1.default.join(appDir, "(auth)");
    const tabsDir = path_1.default.join(appDir, "(tabs)");
    await fs_extra_1.default.ensureDir(authDir);
    await fs_extra_1.default.ensureDir(tabsDir);
    const stateBindings = getExpoAuthStateBindings(state, dataFetching);
    const routeFiles = getExpoAuthRouteFiles(state);
    await fs_extra_1.default.writeFile(path_1.default.join(appDir, "_layout.tsx"), stateBindings.rootLayout, "utf8");
    await fs_extra_1.default.writeFile(path_1.default.join(appDir, "index.tsx"), stateBindings.indexRoute, "utf8");
    await fs_extra_1.default.writeFile(path_1.default.join(authDir, "_layout.tsx"), stateBindings.authLayout, "utf8");
    await fs_extra_1.default.writeFile(path_1.default.join(tabsDir, "_layout.tsx"), stateBindings.tabsLayout, "utf8");
    await fs_extra_1.default.writeFile(path_1.default.join(authDir, "login.tsx"), routeFiles.login, "utf8");
    await fs_extra_1.default.writeFile(path_1.default.join(authDir, "register.tsx"), routeFiles.register, "utf8");
    await fs_extra_1.default.writeFile(path_1.default.join(appDir, "welcome.tsx"), routeFiles.welcome, "utf8");
    await fs_extra_1.default.writeFile(path_1.default.join(tabsDir, "index.tsx"), routeFiles.home, "utf8");
    await fs_extra_1.default.writeFile(path_1.default.join(tabsDir, "profile.tsx"), routeFiles.profile, "utf8");
    await fs_extra_1.default.writeFile(path_1.default.join(tabsDir, "settings.tsx"), routeFiles.settings, "utf8");
    await fs_extra_1.default.remove(path_1.default.join(targetPath, "navigation"));
    await fs_extra_1.default.remove(path_1.default.join(targetPath, "screens"));
}
function getDataFetchingExpoImports(dataFetching) {
    if (dataFetching === "React Query") {
        return `import { QueryClientProvider } from "@tanstack/react-query";\nimport { queryClient } from "../services/queryClient";\n`;
    }
    if (dataFetching === "SWR") {
        return `import { SWRConfig } from "swr";\n`;
    }
    return "";
}
function wrapExpoSlotWithDataFetching(inner, dataFetching) {
    if (dataFetching === "React Query") {
        return `<QueryClientProvider client={queryClient}>\n        ${inner}\n      </QueryClientProvider>`;
    }
    if (dataFetching === "SWR") {
        return `<SWRConfig value={{ revalidateOnFocus: true, revalidateOnReconnect: true }}>\n        ${inner}\n      </SWRConfig>`;
    }
    return inner;
}
function getExpoAuthStateBindings(state, dataFetching = "None") {
    const dfImports = getDataFetchingExpoImports(dataFetching);
    if (state === "Redux Toolkit") {
        const slot = wrapExpoSlotWithDataFetching(`<Slot />`, dataFetching);
        return {
            rootLayout: `import React from "react";
import { Slot } from "expo-router";
import { Provider } from "react-redux";
import { SafeAreaProvider } from "react-native-safe-area-context";
${dfImports}import { store } from "../store/store";

export default function RootLayout() {
  return (
    <SafeAreaProvider>
      <Provider store={store}>
        ${slot}
      </Provider>
    </SafeAreaProvider>
  );
}
`,
            indexRoute: `import React, { useEffect, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ActivityIndicator } from "react-native";
import { Redirect } from "expo-router";
import { useSelector } from "react-redux";
import ScreenLayout from "../components/layout/ScreenLayout";
import type { RootState } from "../store/store";

export default function Index() {
  const token = useSelector((state: RootState) => state.auth.token);
  const [isCheckingWelcome, setIsCheckingWelcome] = useState(true);
  const [hasSeenWelcome, setHasSeenWelcome] = useState(false);

  useEffect(() => {
    const readWelcomeState = async () => {
      try {
        const seen = await AsyncStorage.getItem("@rnskit_has_seen_welcome");
        setHasSeenWelcome(seen === "true");
      } finally {
        setIsCheckingWelcome(false);
      }
    };

    void readWelcomeState();
  }, []);

  if (isCheckingWelcome) {
    return (
      <ScreenLayout centered padded={false}>
        <ActivityIndicator />
      </ScreenLayout>
    );
  }

  if (!hasSeenWelcome) return <Redirect href="/welcome" />;
  return <Redirect href={token ? "/(tabs)" : "/(auth)/login"} />;
}
`,
            authLayout: `import React from "react";
import { Redirect, Stack } from "expo-router";
import { useSelector } from "react-redux";
import type { RootState } from "../../store/store";

export default function AuthLayout() {
  const token = useSelector((state: RootState) => state.auth.token);
  if (token) return <Redirect href="/(tabs)" />;

  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="login" />
      <Stack.Screen name="register" />
    </Stack>
  );
}
`,
            tabsLayout: `import React from "react";
import { Redirect, Tabs } from "expo-router";
import { useSelector } from "react-redux";
import type { RootState } from "../../store/store";

export default function TabsLayout() {
  const token = useSelector((state: RootState) => state.auth.token);
  if (!token) return <Redirect href="/(auth)/login" />;

  return (
    <Tabs screenOptions={{ headerShown: false }}>
      <Tabs.Screen name="index" options={{ title: "Home" }} />
      <Tabs.Screen name="profile" options={{ title: "Profile" }} />
      <Tabs.Screen name="settings" options={{ title: "Settings" }} />
    </Tabs>
  );
}
`,
        };
    }
    if (state === "Zustand") {
        const slot = wrapExpoSlotWithDataFetching(`<Slot />`, dataFetching);
        return {
            rootLayout: `import React from "react";
import { Slot } from "expo-router";
import { SafeAreaProvider } from "react-native-safe-area-context";
${dfImports}
export default function RootLayout() {
  return (
    <SafeAreaProvider>
      ${slot}
    </SafeAreaProvider>
  );
}
`,
            indexRoute: `import React, { useEffect, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ActivityIndicator } from "react-native";
import { Redirect } from "expo-router";
import ScreenLayout from "../components/layout/ScreenLayout";
import { useAuthStore } from "../store/authStore";

export default function Index() {
  const token = useAuthStore((state) => state.token);
  const isHydrated = useAuthStore((state) => state.isHydrated);
  const hydrate = useAuthStore((state) => state.hydrate);
  const [isCheckingWelcome, setIsCheckingWelcome] = useState(true);
  const [hasSeenWelcome, setHasSeenWelcome] = useState(false);

  useEffect(() => {
    void hydrate();
  }, [hydrate]);

  useEffect(() => {
    const readWelcomeState = async () => {
      try {
        const seen = await AsyncStorage.getItem("@rnskit_has_seen_welcome");
        setHasSeenWelcome(seen === "true");
      } finally {
        setIsCheckingWelcome(false);
      }
    };

    void readWelcomeState();
  }, []);

  if (!isHydrated || isCheckingWelcome) {
    return (
      <ScreenLayout centered padded={false}>
        <ActivityIndicator />
      </ScreenLayout>
    );
  }

  if (!hasSeenWelcome) return <Redirect href="/welcome" />;
  return <Redirect href={token ? "/(tabs)" : "/(auth)/login"} />;
}
`,
            authLayout: `import React, { useEffect } from "react";
import { ActivityIndicator } from "react-native";
import { Redirect, Stack } from "expo-router";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { useAuthStore } from "../../store/authStore";

export default function AuthLayout() {
  const token = useAuthStore((state) => state.token);
  const isHydrated = useAuthStore((state) => state.isHydrated);
  const hydrate = useAuthStore((state) => state.hydrate);

  useEffect(() => {
    void hydrate();
  }, [hydrate]);

  if (!isHydrated) {
    return (
      <ScreenLayout centered padded={false}>
        <ActivityIndicator />
      </ScreenLayout>
    );
  }

  if (token) return <Redirect href="/(tabs)" />;

  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="login" />
      <Stack.Screen name="register" />
    </Stack>
  );
}
`,
            tabsLayout: `import React, { useEffect } from "react";
import { ActivityIndicator } from "react-native";
import { Redirect, Tabs } from "expo-router";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { useAuthStore } from "../../store/authStore";

export default function TabsLayout() {
  const token = useAuthStore((state) => state.token);
  const isHydrated = useAuthStore((state) => state.isHydrated);
  const hydrate = useAuthStore((state) => state.hydrate);

  useEffect(() => {
    void hydrate();
  }, [hydrate]);

  if (!isHydrated) {
    return (
      <ScreenLayout centered padded={false}>
        <ActivityIndicator />
      </ScreenLayout>
    );
  }

  if (!token) return <Redirect href="/(auth)/login" />;

  return (
    <Tabs screenOptions={{ headerShown: false }}>
      <Tabs.Screen name="index" options={{ title: "Home" }} />
      <Tabs.Screen name="profile" options={{ title: "Profile" }} />
      <Tabs.Screen name="settings" options={{ title: "Settings" }} />
    </Tabs>
  );
}
`,
        };
    }
    const slot = wrapExpoSlotWithDataFetching(`<Slot />`, dataFetching);
    return {
        rootLayout: `import React from "react";
import { Slot } from "expo-router";
import { SafeAreaProvider } from "react-native-safe-area-context";
${dfImports}import { AuthProvider } from "../context/AuthContext";

export default function RootLayout() {
  return (
    <SafeAreaProvider>
      <AuthProvider>
        ${slot}
      </AuthProvider>
    </SafeAreaProvider>
  );
}
`,
        indexRoute: `import React, { useContext, useEffect, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ActivityIndicator } from "react-native";
import { Redirect } from "expo-router";
import ScreenLayout from "../components/layout/ScreenLayout";
import { AuthContext } from "../context/AuthContext";

export default function Index() {
  const { token, isHydrated } = useContext(AuthContext);
  const [isCheckingWelcome, setIsCheckingWelcome] = useState(true);
  const [hasSeenWelcome, setHasSeenWelcome] = useState(false);

  useEffect(() => {
    const readWelcomeState = async () => {
      try {
        const seen = await AsyncStorage.getItem("@rnskit_has_seen_welcome");
        setHasSeenWelcome(seen === "true");
      } finally {
        setIsCheckingWelcome(false);
      }
    };

    void readWelcomeState();
  }, []);

  if (!isHydrated || isCheckingWelcome) {
    return (
      <ScreenLayout centered padded={false}>
        <ActivityIndicator />
      </ScreenLayout>
    );
  }

  if (!hasSeenWelcome) return <Redirect href="/welcome" />;
  return <Redirect href={token ? "/(tabs)" : "/(auth)/login"} />;
}
`,
        authLayout: `import React, { useContext } from "react";
import { ActivityIndicator } from "react-native";
import { Redirect, Stack } from "expo-router";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { AuthContext } from "../../context/AuthContext";

export default function AuthLayout() {
  const { token, isHydrated } = useContext(AuthContext);

  if (!isHydrated) {
    return (
      <ScreenLayout centered padded={false}>
        <ActivityIndicator />
      </ScreenLayout>
    );
  }

  if (token) return <Redirect href="/(tabs)" />;

  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="login" />
      <Stack.Screen name="register" />
    </Stack>
  );
}
`,
        tabsLayout: `import React, { useContext } from "react";
import { ActivityIndicator } from "react-native";
import { Redirect, Tabs } from "expo-router";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { AuthContext } from "../../context/AuthContext";

export default function TabsLayout() {
  const { token, isHydrated } = useContext(AuthContext);

  if (!isHydrated) {
    return (
      <ScreenLayout centered padded={false}>
        <ActivityIndicator />
      </ScreenLayout>
    );
  }

  if (!token) return <Redirect href="/(auth)/login" />;

  return (
    <Tabs screenOptions={{ headerShown: false }}>
      <Tabs.Screen name="index" options={{ title: "Home" }} />
      <Tabs.Screen name="profile" options={{ title: "Profile" }} />
      <Tabs.Screen name="settings" options={{ title: "Settings" }} />
    </Tabs>
  );
}
`,
    };
}
function getExpoAuthRouteFiles(state) {
    const welcomeRedux = `import React, { useRef, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import {
  FlatList,
  Image,
  ImageSourcePropType,
  Pressable,
  StyleSheet,
  Text,
  useWindowDimensions,
  View,
  ViewToken,
} from "react-native";
import { router } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
import { useSelector } from "react-redux";
import type { RootState } from "../store/store";

type Slide = {
  id: string;
  title: string;
  description: string;
  image: ImageSourcePropType;
};

const slides: Slide[] = [
  {
    id: "1",
    title: "Welcome to RN Starter Kit",
    description: "Build faster with a clean, production-ready app foundation.",
    image: require("../assets/images/react-logo.png"),
  },
  {
    id: "2",
    title: "Structure That Scales",
    description: "Auth, state, and API setup are organized for real-world apps.",
    image: require("../assets/images/partial-react-logo.png"),
  },
  {
    id: "3",
    title: "Make It Yours",
    description: "Swap this welcome flow with your own brand and product journey.",
    image: require("../assets/images/icon.png"),
  },
];

export default function WelcomeScreen() {
  const token = useSelector((state: RootState) => state.auth.token);
  const { width } = useWindowDimensions();
  const [activeIndex, setActiveIndex] = useState(0);
  const listRef = useRef<FlatList<Slide>>(null);

  const onViewableItemsChanged = useRef(
    ({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
      if (viewableItems[0]?.index != null) {
        setActiveIndex(viewableItems[0].index);
      }
    },
  ).current;

  const viewabilityConfig = useRef({ viewAreaCoveragePercentThreshold: 60 }).current;

  const handleNext = async () => {
    const nextIndex = activeIndex + 1;
    if (nextIndex < slides.length) {
      listRef.current?.scrollToIndex({ index: nextIndex, animated: true });
      return;
    }
    await AsyncStorage.setItem("@rnskit_has_seen_welcome", "true");
    router.replace(token ? "/(tabs)" : "/(auth)/login");
  };

  const isLastSlide = activeIndex === slides.length - 1;

  return (
    <SafeAreaView style={styles.safeArea}>
      <FlatList
        ref={listRef}
        data={slides}
        horizontal
        pagingEnabled
        bounces={false}
        keyExtractor={(item) => item.id}
        showsHorizontalScrollIndicator={false}
        renderItem={({ item }) => (
          <View style={[styles.slide, { width }]}>
            <Image source={item.image} style={styles.image} resizeMode="contain" />
            <Text style={styles.title}>{item.title}</Text>
            <Text style={styles.description}>{item.description}</Text>
          </View>
        )}
        onViewableItemsChanged={onViewableItemsChanged}
        viewabilityConfig={viewabilityConfig}
      />

      <View style={styles.footer}>
        <View style={styles.dotsRow}>
          {slides.map((slide, index) => (
            <View
              key={slide.id}
              style={[styles.dot, index === activeIndex && styles.dotActive]}
            />
          ))}
        </View>

        <Pressable style={styles.button} onPress={() => void handleNext()}>
          <Text style={styles.buttonLabel}>{isLastSlide ? "Get Started" : "Next"}</Text>
        </Pressable>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: "#F7F7F9",
  },
  slide: {
    flex: 1,
    paddingHorizontal: 24,
    justifyContent: "center",
    alignItems: "center",
  },
  image: {
    width: 220,
    height: 220,
    marginBottom: 28,
  },
  title: {
    fontSize: 28,
    fontWeight: "700",
    color: "#111827",
    textAlign: "center",
    marginBottom: 12,
  },
  description: {
    fontSize: 16,
    lineHeight: 24,
    color: "#4B5563",
    textAlign: "center",
    maxWidth: 320,
  },
  footer: {
    paddingHorizontal: 24,
    paddingBottom: 28,
    gap: 20,
  },
  dotsRow: {
    flexDirection: "row",
    justifyContent: "center",
    gap: 8,
  },
  dot: {
    width: 8,
    height: 8,
    borderRadius: 999,
    backgroundColor: "#D1D5DB",
  },
  dotActive: {
    width: 24,
    backgroundColor: "#111827",
  },
  button: {
    height: 52,
    borderRadius: 12,
    backgroundColor: "#111827",
    alignItems: "center",
    justifyContent: "center",
  },
  buttonLabel: {
    color: "#FFFFFF",
    fontSize: 16,
    fontWeight: "600",
  },
});
`;
    const welcomeZustand = `import React, { useEffect, useRef, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import {
  ActivityIndicator,
  FlatList,
  Image,
  ImageSourcePropType,
  Pressable,
  StyleSheet,
  Text,
  useWindowDimensions,
  View,
  ViewToken,
} from "react-native";
import { router } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
import { useAuthStore } from "../store/authStore";
import ScreenLayout from "../components/layout/ScreenLayout";

type Slide = {
  id: string;
  title: string;
  description: string;
  image: ImageSourcePropType;
};

const slides: Slide[] = [
  {
    id: "1",
    title: "Welcome to RN Starter Kit",
    description: "Build faster with a clean, production-ready app foundation.",
    image: require("../assets/images/react-logo.png"),
  },
  {
    id: "2",
    title: "Structure That Scales",
    description: "Auth, state, and API setup are organized for real-world apps.",
    image: require("../assets/images/partial-react-logo.png"),
  },
  {
    id: "3",
    title: "Make It Yours",
    description: "Swap this welcome flow with your own brand and product journey.",
    image: require("../assets/images/icon.png"),
  },
];

export default function WelcomeScreen() {
  const token = useAuthStore((state) => state.token);
  const isHydrated = useAuthStore((state) => state.isHydrated);
  const hydrate = useAuthStore((state) => state.hydrate);
  const { width } = useWindowDimensions();
  const [activeIndex, setActiveIndex] = useState(0);
  const listRef = useRef<FlatList<Slide>>(null);

  useEffect(() => {
    void hydrate();
  }, [hydrate]);

  const onViewableItemsChanged = useRef(
    ({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
      if (viewableItems[0]?.index != null) {
        setActiveIndex(viewableItems[0].index);
      }
    },
  ).current;

  const viewabilityConfig = useRef({ viewAreaCoveragePercentThreshold: 60 }).current;

  const handleNext = async () => {
    const nextIndex = activeIndex + 1;
    if (nextIndex < slides.length) {
      listRef.current?.scrollToIndex({ index: nextIndex, animated: true });
      return;
    }
    await AsyncStorage.setItem("@rnskit_has_seen_welcome", "true");
    router.replace(token ? "/(tabs)" : "/(auth)/login");
  };

  if (!isHydrated) {
    return (
      <ScreenLayout centered padded={false}>
        <ActivityIndicator />
      </ScreenLayout>
    );
  }

  const isLastSlide = activeIndex === slides.length - 1;

  return (
    <SafeAreaView style={styles.safeArea}>
      <FlatList
        ref={listRef}
        data={slides}
        horizontal
        pagingEnabled
        bounces={false}
        keyExtractor={(item) => item.id}
        showsHorizontalScrollIndicator={false}
        renderItem={({ item }) => (
          <View style={[styles.slide, { width }]}>
            <Image source={item.image} style={styles.image} resizeMode="contain" />
            <Text style={styles.title}>{item.title}</Text>
            <Text style={styles.description}>{item.description}</Text>
          </View>
        )}
        onViewableItemsChanged={onViewableItemsChanged}
        viewabilityConfig={viewabilityConfig}
      />

      <View style={styles.footer}>
        <View style={styles.dotsRow}>
          {slides.map((slide, index) => (
            <View
              key={slide.id}
              style={[styles.dot, index === activeIndex && styles.dotActive]}
            />
          ))}
        </View>

        <Pressable style={styles.button} onPress={() => void handleNext()}>
          <Text style={styles.buttonLabel}>{isLastSlide ? "Get Started" : "Next"}</Text>
        </Pressable>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: "#F7F7F9",
  },
  slide: {
    flex: 1,
    paddingHorizontal: 24,
    justifyContent: "center",
    alignItems: "center",
  },
  image: {
    width: 220,
    height: 220,
    marginBottom: 28,
  },
  title: {
    fontSize: 28,
    fontWeight: "700",
    color: "#111827",
    textAlign: "center",
    marginBottom: 12,
  },
  description: {
    fontSize: 16,
    lineHeight: 24,
    color: "#4B5563",
    textAlign: "center",
    maxWidth: 320,
  },
  footer: {
    paddingHorizontal: 24,
    paddingBottom: 28,
    gap: 20,
  },
  dotsRow: {
    flexDirection: "row",
    justifyContent: "center",
    gap: 8,
  },
  dot: {
    width: 8,
    height: 8,
    borderRadius: 999,
    backgroundColor: "#D1D5DB",
  },
  dotActive: {
    width: 24,
    backgroundColor: "#111827",
  },
  button: {
    height: 52,
    borderRadius: 12,
    backgroundColor: "#111827",
    alignItems: "center",
    justifyContent: "center",
  },
  buttonLabel: {
    color: "#FFFFFF",
    fontSize: 16,
    fontWeight: "600",
  },
});
`;
    const welcomeContext = `import React, { useContext, useRef, useState } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import {
  ActivityIndicator,
  FlatList,
  Image,
  ImageSourcePropType,
  Pressable,
  StyleSheet,
  Text,
  useWindowDimensions,
  View,
  ViewToken,
} from "react-native";
import { router } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
import ScreenLayout from "../components/layout/ScreenLayout";
import { AuthContext } from "../context/AuthContext";

type Slide = {
  id: string;
  title: string;
  description: string;
  image: ImageSourcePropType;
};

const slides: Slide[] = [
  {
    id: "1",
    title: "Welcome to RN Starter Kit",
    description: "Build faster with a clean, production-ready app foundation.",
    image: require("../assets/images/react-logo.png"),
  },
  {
    id: "2",
    title: "Structure That Scales",
    description: "Auth, state, and API setup are organized for real-world apps.",
    image: require("../assets/images/partial-react-logo.png"),
  },
  {
    id: "3",
    title: "Make It Yours",
    description: "Swap this welcome flow with your own brand and product journey.",
    image: require("../assets/images/icon.png"),
  },
];

export default function WelcomeScreen() {
  const { token, isHydrated } = useContext(AuthContext);
  const { width } = useWindowDimensions();
  const [activeIndex, setActiveIndex] = useState(0);
  const listRef = useRef<FlatList<Slide>>(null);

  const onViewableItemsChanged = useRef(
    ({ viewableItems }: { viewableItems: Array<ViewToken> }) => {
      if (viewableItems[0]?.index != null) {
        setActiveIndex(viewableItems[0].index);
      }
    },
  ).current;

  const viewabilityConfig = useRef({ viewAreaCoveragePercentThreshold: 60 }).current;

  const handleNext = async () => {
    const nextIndex = activeIndex + 1;
    if (nextIndex < slides.length) {
      listRef.current?.scrollToIndex({ index: nextIndex, animated: true });
      return;
    }
    await AsyncStorage.setItem("@rnskit_has_seen_welcome", "true");
    router.replace(token ? "/(tabs)" : "/(auth)/login");
  };

  if (!isHydrated) {
    return (
      <ScreenLayout centered padded={false}>
        <ActivityIndicator />
      </ScreenLayout>
    );
  }

  const isLastSlide = activeIndex === slides.length - 1;

  return (
    <SafeAreaView style={styles.safeArea}>
      <FlatList
        ref={listRef}
        data={slides}
        horizontal
        pagingEnabled
        bounces={false}
        keyExtractor={(item) => item.id}
        showsHorizontalScrollIndicator={false}
        renderItem={({ item }) => (
          <View style={[styles.slide, { width }]}>
            <Image source={item.image} style={styles.image} resizeMode="contain" />
            <Text style={styles.title}>{item.title}</Text>
            <Text style={styles.description}>{item.description}</Text>
          </View>
        )}
        onViewableItemsChanged={onViewableItemsChanged}
        viewabilityConfig={viewabilityConfig}
      />

      <View style={styles.footer}>
        <View style={styles.dotsRow}>
          {slides.map((slide, index) => (
            <View
              key={slide.id}
              style={[styles.dot, index === activeIndex && styles.dotActive]}
            />
          ))}
        </View>

        <Pressable style={styles.button} onPress={() => void handleNext()}>
          <Text style={styles.buttonLabel}>{isLastSlide ? "Get Started" : "Next"}</Text>
        </Pressable>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: "#F7F7F9",
  },
  slide: {
    flex: 1,
    paddingHorizontal: 24,
    justifyContent: "center",
    alignItems: "center",
  },
  image: {
    width: 220,
    height: 220,
    marginBottom: 28,
  },
  title: {
    fontSize: 28,
    fontWeight: "700",
    color: "#111827",
    textAlign: "center",
    marginBottom: 12,
  },
  description: {
    fontSize: 16,
    lineHeight: 24,
    color: "#4B5563",
    textAlign: "center",
    maxWidth: 320,
  },
  footer: {
    paddingHorizontal: 24,
    paddingBottom: 28,
    gap: 20,
  },
  dotsRow: {
    flexDirection: "row",
    justifyContent: "center",
    gap: 8,
  },
  dot: {
    width: 8,
    height: 8,
    borderRadius: 999,
    backgroundColor: "#D1D5DB",
  },
  dotActive: {
    width: 24,
    backgroundColor: "#111827",
  },
  button: {
    height: 52,
    borderRadius: 12,
    backgroundColor: "#111827",
    alignItems: "center",
    justifyContent: "center",
  },
  buttonLabel: {
    color: "#FFFFFF",
    fontSize: 16,
    fontWeight: "600",
  },
});
`;
    const registerRedux = `import React, { useState } from "react";
import { Button, Text, TextInput } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { useDispatch } from "react-redux";
import { registerApi } from "../../api";
import { login } from "../../store/authSlice";

export default function RegisterScreen() {
  const dispatch = useDispatch();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  const handleRegister = async () => {
    try {
      setError("");
      const token = await registerApi(email, password);
      dispatch(login(token));
    } catch (err) {
      setError(err instanceof Error ? err.message : "Registration failed");
    }
  };

  return (
    <ScreenLayout>
      <TextInput
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
        autoCorrect={false}
        keyboardType="email-address"
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      <TextInput
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
      <Button title="Register" onPress={() => void handleRegister()} />
    </ScreenLayout>
  );
}
`;
    const registerZustand = `import React, { useState } from "react";
import { Button, Text, TextInput } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { registerApi } from "../../api";
import { useAuthStore } from "../../store/authStore";

export default function RegisterScreen() {
  const login = useAuthStore((state) => state.login);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  const handleRegister = async () => {
    try {
      setError("");
      const token = await registerApi(email, password);
      await login(token);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Registration failed");
    }
  };

  return (
    <ScreenLayout>
      <TextInput
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
        autoCorrect={false}
        keyboardType="email-address"
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      <TextInput
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
      <Button title="Register" onPress={() => void handleRegister()} />
    </ScreenLayout>
  );
}
`;
    const registerContext = `import React, { useContext, useState } from "react";
import { Button, Text, TextInput } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { registerApi } from "../../api";
import { AuthContext } from "../../context/AuthContext";

export default function RegisterScreen() {
  const { login } = useContext(AuthContext);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  const handleRegister = async () => {
    try {
      setError("");
      const token = await registerApi(email, password);
      await login(token);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Registration failed");
    }
  };

  return (
    <ScreenLayout>
      <TextInput
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
        autoCorrect={false}
        keyboardType="email-address"
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      <TextInput
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
      <Button title="Register" onPress={() => void handleRegister()} />
    </ScreenLayout>
  );
}
`;
    const profile = `import React from "react";
import { Text } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";

export default function ProfileScreen() {
  return (
    <ScreenLayout>
      <Text>ProfileScreen</Text>
    </ScreenLayout>
  );
}
`;
    const settings = `import React from "react";
import { Text } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";

export default function SettingsScreen() {
  return (
    <ScreenLayout>
      <Text>SettingsScreen</Text>
    </ScreenLayout>
  );
}
`;
    if (state === "Redux Toolkit") {
        return {
            welcome: welcomeRedux,
            login: `import React, { useState } from "react";
import { Button, Text, TextInput } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { useDispatch } from "react-redux";
import { loginApi } from "../../api";
import { login } from "../../store/authSlice";

export default function LoginScreen() {
  const dispatch = useDispatch();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  const handleLogin = async () => {
    try {
      setError("");
      const token = await loginApi(email, password);
      dispatch(login(token));
    } catch (err) {
      setError(err instanceof Error ? err.message : "Login failed");
    }
  };

  return (
    <ScreenLayout>
      <TextInput
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
        autoCorrect={false}
        keyboardType="email-address"
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      <TextInput
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
      <Button title="Login" onPress={() => void handleLogin()} />
    </ScreenLayout>
  );
}
`,
            register: registerRedux,
            home: `import React from "react";
import { Button, Text } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { useDispatch } from "react-redux";
import { logout } from "../../store/authSlice";

export default function HomeScreen() {
  const dispatch = useDispatch();

  return (
    <ScreenLayout>
      <Text>Welcome Home!</Text>
      <Button title="Logout" onPress={() => dispatch(logout())} />
    </ScreenLayout>
  );
}
`,
            profile,
            settings,
        };
    }
    if (state === "Zustand") {
        return {
            welcome: welcomeZustand,
            login: `import React, { useState } from "react";
import { Button, Text, TextInput } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { loginApi } from "../../api";
import { useAuthStore } from "../../store/authStore";

export default function LoginScreen() {
  const login = useAuthStore((state) => state.login);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  const handleLogin = async () => {
    try {
      setError("");
      const token = await loginApi(email, password);
      await login(token);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Login failed");
    }
  };

  return (
    <ScreenLayout>
      <TextInput
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
        autoCorrect={false}
        keyboardType="email-address"
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      <TextInput
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
      <Button title="Login" onPress={() => void handleLogin()} />
    </ScreenLayout>
  );
}
`,
            register: registerZustand,
            home: `import React from "react";
import { Button, Text } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { useAuthStore } from "../../store/authStore";

export default function HomeScreen() {
  const logout = useAuthStore((state) => state.logout);

  return (
    <ScreenLayout>
      <Text>Welcome Home!</Text>
      <Button title="Logout" onPress={() => void logout()} />
    </ScreenLayout>
  );
}
`,
            profile,
            settings,
        };
    }
    return {
        welcome: welcomeContext,
        login: `import React, { useContext, useState } from "react";
import { Button, Text, TextInput } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { loginApi } from "../../api";
import { AuthContext } from "../../context/AuthContext";

export default function LoginScreen() {
  const { login } = useContext(AuthContext);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  const handleLogin = async () => {
    try {
      setError("");
      const token = await loginApi(email, password);
      await login(token);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Login failed");
    }
  };

  return (
    <ScreenLayout>
      <TextInput
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
        autoCorrect={false}
        keyboardType="email-address"
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      <TextInput
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        placeholderTextColor="#888"
        style={{
          borderWidth: 1,
          borderColor: "#ccc",
          borderRadius: 8,
          paddingHorizontal: 12,
          paddingVertical: 10,
          color: "#111",
          marginBottom: 12,
        }}
      />
      {!!error && <Text style={{ color: "red", marginBottom: 8 }}>{error}</Text>}
      <Button title="Login" onPress={() => void handleLogin()} />
    </ScreenLayout>
  );
}
`,
        register: registerContext,
        home: `import React, { useContext } from "react";
import { Button, Text } from "react-native";
import ScreenLayout from "../../components/layout/ScreenLayout";
import { AuthContext } from "../../context/AuthContext";

export default function HomeScreen() {
  const { logout } = useContext(AuthContext);

  return (
    <ScreenLayout>
      <Text>Welcome Home!</Text>
      <Button title="Logout" onPress={() => void logout()} />
    </ScreenLayout>
  );
}
`,
        profile,
        settings,
    };
}
function getDataFetchingCliImports(dataFetching) {
    if (dataFetching === "React Query") {
        return `import { QueryClientProvider } from "@tanstack/react-query";\nimport { queryClient } from "./services/queryClient";\n`;
    }
    if (dataFetching === "SWR") {
        return `import { SWRConfig } from "swr";\n`;
    }
    return "";
}
function wrapCliNavWithDataFetching(inner, dataFetching) {
    if (dataFetching === "React Query") {
        return `<QueryClientProvider client={queryClient}>\n        ${inner}\n      </QueryClientProvider>`;
    }
    if (dataFetching === "SWR") {
        return `<SWRConfig value={{ revalidateOnFocus: true, revalidateOnReconnect: true }}>\n        ${inner}\n      </SWRConfig>`;
    }
    return inner;
}
async function writeAuthAppShell(targetPath, state, dataFetching = "None") {
    const appPath = path_1.default.join(targetPath, "App.tsx");
    const dfImports = getDataFetchingCliImports(dataFetching);
    const contextInner = wrapCliNavWithDataFetching(`<NavigationContainer>\n          <ProtectedStack />\n        </NavigationContainer>`, dataFetching);
    const reduxInner = wrapCliNavWithDataFetching(`<NavigationContainer>\n          <ProtectedStack />\n        </NavigationContainer>`, dataFetching);
    const zustandInner = wrapCliNavWithDataFetching(`<NavigationContainer>\n        <ProtectedStack />\n      </NavigationContainer>`, dataFetching);
    const byState = {
        "Context API": `import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { SafeAreaProvider } from "react-native-safe-area-context";
${dfImports}import { AuthProvider } from "./context/AuthContext";
import ProtectedStack from "./navigation/ProtectedStack";

export default function App() {
  return (
    <SafeAreaProvider>
      <AuthProvider>
        ${contextInner}
      </AuthProvider>
    </SafeAreaProvider>
  );
}
`,
        "Redux Toolkit": `import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { Provider } from "react-redux";
import { SafeAreaProvider } from "react-native-safe-area-context";
${dfImports}import ProtectedStack from "./navigation/ProtectedStack";
import { store } from "./store/store";

export default function App() {
  return (
    <SafeAreaProvider>
      <Provider store={store}>
        ${reduxInner}
      </Provider>
    </SafeAreaProvider>
  );
}
`,
        Zustand: `import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { SafeAreaProvider } from "react-native-safe-area-context";
${dfImports}import ProtectedStack from "./navigation/ProtectedStack";

export default function App() {
  return (
    <SafeAreaProvider>
      ${zustandInner}
    </SafeAreaProvider>
  );
}
`,
    };
    const content = byState[state];
    if (!content)
        return;
    await fs_extra_1.default.writeFile(appPath, content, "utf8");
}
async function configureTsconfigAlias(targetPath, absoluteImports) {
    const tsconfigPath = path_1.default.join(targetPath, "tsconfig.json");
    if (!(await fs_extra_1.default.pathExists(tsconfigPath)))
        return;
    const tsconfig = await fs_extra_1.default.readJson(tsconfigPath);
    tsconfig.compilerOptions = tsconfig.compilerOptions || {};
    if (absoluteImports) {
        tsconfig.compilerOptions.baseUrl = ".";
        tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
        tsconfig.compilerOptions.paths["@/*"] = ["./*"];
    }
    else if (tsconfig.compilerOptions.paths) {
        delete tsconfig.compilerOptions.paths["@/*"];
        if (Object.keys(tsconfig.compilerOptions.paths).length === 0) {
            delete tsconfig.compilerOptions.paths;
        }
    }
    await fs_extra_1.default.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
}
async function writeCliBabelConfig(targetPath, options) {
    const babelConfigPath = path_1.default.join(targetPath, "babel.config.js");
    if (!(await fs_extra_1.default.pathExists(babelConfigPath)))
        return;
    const plugins = [];
    if (options.useDotenv)
        plugins.push("'module:react-native-dotenv'");
    if (options.useAbsoluteImports) {
        plugins.push("['module-resolver', { root: ['./'], alias: { '@': './' } }]");
    }
    const pluginsLine = plugins.length > 0 ? `,\n  plugins: [${plugins.join(", ")}]` : "";
    const content = `module.exports = {\n  presets: ['module:@react-native/babel-preset']${pluginsLine},\n};\n`;
    await fs_extra_1.default.writeFile(babelConfigPath, content, "utf8");
}
async function usesDotenv(targetPath) {
    const envCliPath = path_1.default.join(targetPath, "config/env.cli.ts");
    return fs_extra_1.default.pathExists(envCliPath);
}
async function ensureDependencies(targetPath, patch) {
    const packageJsonPath = path_1.default.join(targetPath, "package.json");
    if (await fs_extra_1.default.pathExists(packageJsonPath)) {
        const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
        if (patch.dependencies) {
            packageJson.dependencies = packageJson.dependencies || {};
            for (const [pkg, version] of Object.entries(patch.dependencies)) {
                if (!packageJson.dependencies[pkg]) {
                    packageJson.dependencies[pkg] = version;
                }
            }
        }
        if (patch.devDependencies) {
            packageJson.devDependencies = packageJson.devDependencies || {};
            for (const [pkg, version] of Object.entries(patch.devDependencies)) {
                if (!packageJson.devDependencies[pkg]) {
                    packageJson.devDependencies[pkg] = version;
                }
            }
        }
        await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
    }
}
async function writeIfMissing(filePath, content) {
    await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
    if (!(await fs_extra_1.default.pathExists(filePath))) {
        await fs_extra_1.default.writeFile(filePath, content, "utf8");
    }
}
async function configureNavigationTypes(targetPath, options) {
    if (options.platform !== "React Native CLI" || !options.auth)
        return;
    await writeIfMissing(path_1.default.join(targetPath, "types/navigation.ts"), `import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import type { BottomTabScreenProps } from "@react-navigation/bottom-tabs";
import type { CompositeScreenProps } from "@react-navigation/native";

export type AuthStackParamList = {
  Login: undefined;
  Register: undefined;
};

export type MainTabParamList = {
  Home: undefined;
  Profile: undefined;
  Settings: undefined;
};

export type AuthStackScreenProps<T extends keyof AuthStackParamList> =
  NativeStackScreenProps<AuthStackParamList, T>;

export type MainTabScreenProps<T extends keyof MainTabParamList> =
  CompositeScreenProps<
    BottomTabScreenProps<MainTabParamList, T>,
    NativeStackScreenProps<AuthStackParamList>
  >;
`);
}
async function configureTheme(targetPath) {
    await writeIfMissing(path_1.default.join(targetPath, "theme/colors.ts"), `export const colors = {
  primary: "#111827",
  secondary: "#6B7280",
  background: "#F7F7F9",
  surface: "#FFFFFF",
  error: "#EF4444",
  success: "#10B981",
  warning: "#F59E0B",
  text: {
    primary: "#111827",
    secondary: "#4B5563",
    disabled: "#9CA3AF",
    inverse: "#FFFFFF",
  },
  border: "#E5E7EB",
};

export type AppColors = typeof colors;
`);
    await writeIfMissing(path_1.default.join(targetPath, "theme/spacing.ts"), `export const spacing = {
  xs: 4,
  sm: 8,
  md: 16,
  lg: 24,
  xl: 32,
  xxl: 48,
} as const;

export type AppSpacing = typeof spacing;
`);
    await writeIfMissing(path_1.default.join(targetPath, "theme/typography.ts"), `import { StyleSheet } from "react-native";

export const typography = StyleSheet.create({
  h1: { fontSize: 32, fontWeight: "700", lineHeight: 40 },
  h2: { fontSize: 24, fontWeight: "700", lineHeight: 32 },
  h3: { fontSize: 20, fontWeight: "600", lineHeight: 28 },
  body: { fontSize: 16, fontWeight: "400", lineHeight: 24 },
  bodySmall: { fontSize: 14, fontWeight: "400", lineHeight: 20 },
  caption: { fontSize: 12, fontWeight: "400", lineHeight: 16 },
  label: { fontSize: 14, fontWeight: "600", lineHeight: 20 },
});
`);
    await writeIfMissing(path_1.default.join(targetPath, "theme/index.ts"), `export { colors } from "./colors";
export { spacing } from "./spacing";
export { typography } from "./typography";
export type { AppColors } from "./colors";
export type { AppSpacing } from "./spacing";
`);
}
async function configureErrorBoundary(targetPath) {
    await writeIfMissing(path_1.default.join(targetPath, "components/ErrorBoundary.tsx"), `import React, { Component, type ReactNode } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";

type Props = {
  children: ReactNode;
  fallback?: ReactNode;
  onReset?: () => void;
};

type State = {
  hasError: boolean;
  error: Error | null;
};

export class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, info: { componentStack: string }) {
    console.error("ErrorBoundary caught:", error, info.componentStack);
  }

  handleReset = () => {
    this.setState({ hasError: false, error: null });
    this.props.onReset?.();
  };

  render() {
    if (this.state.hasError) {
      if (this.props.fallback) return this.props.fallback;
      return (
        <View style={styles.container}>
          <Text style={styles.title}>Something went wrong</Text>
          <Text style={styles.message}>{this.state.error?.message}</Text>
          <Pressable style={styles.button} onPress={this.handleReset}>
            <Text style={styles.buttonLabel}>Try again</Text>
          </Pressable>
        </View>
      );
    }
    return this.props.children;
  }
}

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: "center", justifyContent: "center", padding: 24 },
  title: { fontSize: 20, fontWeight: "700", color: "#111827", marginBottom: 8 },
  message: { fontSize: 14, color: "#6B7280", textAlign: "center", marginBottom: 24 },
  button: { paddingHorizontal: 24, paddingVertical: 12, backgroundColor: "#111827", borderRadius: 8 },
  buttonLabel: { color: "#FFFFFF", fontSize: 14, fontWeight: "600" },
});
`);
}
async function configureTesting(targetPath) {
    // Pin react-test-renderer to the same React version already in the project
    // to avoid ERESOLVE peer dep conflicts (react-test-renderer requires exact react match)
    const pkgJsonPath = path_1.default.join(targetPath, "package.json");
    let reactVersion = "19.0.0";
    if (await fs_extra_1.default.pathExists(pkgJsonPath)) {
        const pkg = await fs_extra_1.default.readJson(pkgJsonPath);
        reactVersion =
            pkg.dependencies?.react ?? pkg.devDependencies?.react ?? "19.0.0";
        // Strip any semver range prefix so the pin is exact
        reactVersion = reactVersion.replace(/^[\^~]/, "");
    }
    await ensureDependencies(targetPath, {
        devDependencies: {
            "@testing-library/react-native": "^13.2.0",
            "@testing-library/jest-native": "^5.4.3",
            "react-test-renderer": reactVersion,
        },
    });
    // Wire @testing-library into jest.config.js so matchers like toBeVisible() work
    const jestConfigPath = path_1.default.join(targetPath, "jest.config.js");
    if (await fs_extra_1.default.pathExists(jestConfigPath)) {
        const content = await fs_extra_1.default.readFile(jestConfigPath, "utf8");
        if (!content.includes("setupFilesAfterEnv")) {
            await fs_extra_1.default.writeFile(jestConfigPath, `module.exports = {
  preset: 'react-native',
  setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
  transformIgnorePatterns: [
    'node_modules/(?!(react-native|@react-native|@react-navigation|react-native-reanimated)/)',
  ],
};\n`, "utf8");
        }
    }
}
async function stampVersion(targetPath) {
    const packageJsonPath = path_1.default.join(targetPath, "package.json");
    if (!(await fs_extra_1.default.pathExists(packageJsonPath)))
        return;
    // Resolve the CLI's own package.json regardless of cwd
    const cliPkgCandidates = [
        path_1.default.resolve(__dirname, "../../../package.json"), // dist/src/generators -> root
        path_1.default.resolve(__dirname, "../../package.json"), // ts-node: src/generators -> root
        path_1.default.resolve(process.cwd(), "package.json"), // fallback
    ];
    let cliVersion = "unknown";
    for (const candidate of cliPkgCandidates) {
        if (await fs_extra_1.default.pathExists(candidate)) {
            const cliPkg = await fs_extra_1.default.readJson(candidate);
            if (cliPkg.name?.includes("rnstarterkit")) {
                cliVersion = cliPkg.version ?? "unknown";
                break;
            }
        }
    }
    const projectPkg = await fs_extra_1.default.readJson(packageJsonPath);
    projectPkg.rnstarterkit = cliVersion;
    await fs_extra_1.default.writeJson(packageJsonPath, projectPkg, { spaces: 2 });
}
async function configureEnv(targetPath, platform) {
    // Always create .env.example so devs know where to put secrets
    await writeIfMissing(path_1.default.join(targetPath, ".env.example"), `# Environment variables — copy to .env and fill in values
# Never commit .env to source control

API_URL=https://api.example.com
APP_ENV=development
`);
    // Ensure .env is gitignored
    const gitignorePath = path_1.default.join(targetPath, ".gitignore");
    if (await fs_extra_1.default.pathExists(gitignorePath)) {
        const content = await fs_extra_1.default.readFile(gitignorePath, "utf8");
        if (!content.includes(".env")) {
            await fs_extra_1.default.appendFile(gitignorePath, "\n# Environment variables\n.env\n");
        }
    }
    if (platform === "React Native CLI") {
        // react-native-config: reads .env and exposes via Config.*
        await ensureDependencies(targetPath, {
            dependencies: { "react-native-config": "^1.5.1" },
        });
        await writeIfMissing(path_1.default.join(targetPath, "src", "config", "env.ts"), `import Config from 'react-native-config';

export const ENV = {
  API_URL: Config.API_URL ?? 'https://api.example.com',
  APP_ENV: Config.APP_ENV ?? 'development',
} as const;
`);
    }
    else {
        // Expo: use expo-constants / process.env (works with babel inline transform)
        await writeIfMissing(path_1.default.join(targetPath, "src", "config", "env.ts"), `// Expo exposes env vars via process.env (set in app.config.js extra field)
// or via babel-plugin-transform-inline-environment-variables

export const ENV = {
  API_URL: process.env.EXPO_PUBLIC_API_URL ?? 'https://api.example.com',
  APP_ENV: process.env.EXPO_PUBLIC_APP_ENV ?? 'development',
} as const;
`);
    }
}
async function configureHusky(targetPath) {
    await ensureDependencies(targetPath, {
        devDependencies: {
            husky: "^9.1.0",
            "lint-staged": "^15.2.0",
        },
    });
    const packageJsonPath = path_1.default.join(targetPath, "package.json");
    if (await fs_extra_1.default.pathExists(packageJsonPath)) {
        const packageJson = await fs_extra_1.default.readJson(packageJsonPath);
        packageJson.scripts = packageJson.scripts || {};
        if (!packageJson.scripts.prepare) {
            packageJson.scripts.prepare = "husky";
        }
        if (!packageJson["lint-staged"]) {
            packageJson["lint-staged"] = {
                "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
            };
        }
        await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
    }
    const huskyDir = path_1.default.join(targetPath, ".husky");
    await fs_extra_1.default.ensureDir(huskyDir);
    await writeIfMissing(path_1.default.join(huskyDir, "pre-commit"), `npx lint-staged\n`);
    // Make pre-commit executable
    try {
        await fs_extra_1.default.chmod(path_1.default.join(huskyDir, "pre-commit"), 0o755);
    }
    catch {
        // chmod may not work on all platforms, safe to ignore
    }
}
async function configureCi(targetPath) {
    const workflowsDir = path_1.default.join(targetPath, ".github", "workflows");
    await fs_extra_1.default.ensureDir(workflowsDir);
    await writeIfMissing(path_1.default.join(workflowsDir, "ci.yml"), `name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm install
      - run: npm run lint

  test:
    name: Test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm install
      - run: npm test -- --passWithNoTests
`);
}
async function resolveTemplateRoot() {
    const candidates = [
        path_1.default.join(__dirname, "../templates"), // ts-node: src/generators -> src/templates
        path_1.default.join(__dirname, "../../templates"), // dist/src/generators -> dist/templates
        path_1.default.join(__dirname, "../../../src/templates"), // fallback for packaged source templates
        path_1.default.join(process.cwd(), "src/templates"), // local fallback during development
    ];
    for (const candidate of candidates) {
        if (await fs_extra_1.default.pathExists(candidate))
            return candidate;
    }
    throw new Error("Template root not found.");
}
function shouldCopyPath(src, templatePath) {
    const relative = path_1.default.relative(templatePath, src).replace(/\\/g, "/");
    if (!relative || relative === ".")
        return true;
    const blockedSegments = [
        ".git",
        "node_modules",
        "ios/Pods",
        "android/.gradle",
        "vendor/bundle",
    ];
    return !blockedSegments.some((segment) => relative === segment ||
        relative.startsWith(`${segment}/`) ||
        relative.includes(`/${segment}/`));
}
function isTextFile(filePath) {
    const extension = path_1.default.extname(filePath).toLowerCase();
    const textExtensions = new Set([
        ".js",
        ".jsx",
        ".ts",
        ".tsx",
        ".json",
        ".md",
        ".xml",
        ".gradle",
        ".properties",
        ".kt",
        ".kts",
        ".swift",
        ".m",
        ".mm",
        ".h",
        ".java",
        ".rb",
        ".plist",
        ".pbxproj",
        ".yml",
        ".yaml",
        ".txt",
        ".config",
        ".lock",
        ".xcprivacy",
        ".storyboard",
    ]);
    const base = path_1.default.basename(filePath).toLowerCase();
    return textExtensions.has(extension) || base === "podfile" || base === "gemfile";
}
