/**
 * Create Command
 *
 * One-shot project creation with templates.
 * Usage: quickback create <template> <app-name>
 * Example: quickback create betterauth-d1-cloudflare my-app
 */
import { existsSync, mkdirSync, writeFileSync } from "fs";
import { join, resolve } from "path";
import { spawnSync } from "child_process";
import pc from "picocolors";
import ora from "ora";
import prompts from "prompts";
import { getTemplate, listTemplates, formatTemplateList, TEMPLATE_ALIASES, } from "../templates/registry.js";
import { callCompiler, getApiUrl } from "../lib/api-client.js";
import { ensureAuthForCompile } from "../lib/auth.js";
import { loadConfig, loadFeatures, findConfigPath, findFeaturesDir } from "../lib/file-loader.js";
import { writeFiles, writeFilesWithMerge } from "../lib/file-writer.js";
import { partitionGeneratedFiles } from "../lib/artifact-routing.js";
import { login } from "./login.js";
// ============================================================================
// Main Create Function
// ============================================================================
export async function create(args) {
    console.log(pc.bold("\nQuickback Create") + pc.gray(" · One-shot project generator\n"));
    try {
        const options = await parseCreateArgs(args);
        if (options.dryRun) {
            await performDryRun(options);
            return;
        }
        await runCreate(options);
    }
    catch (error) {
        console.error(pc.red("\nProject creation failed:"));
        console.error(pc.red(String(error)));
        process.exit(1);
    }
}
/**
 * Shared scaffold + compile pipeline.
 *
 * Order is load-bearing — scaffolding and (optional) Cloudflare D1 setup run
 * **before** the auth gate. A brand-new user gets through the entire
 * "creating files / writing config / setting up Cloudflare" phase before
 * being asked to sign in, so they're already invested by the time the
 * device-flow code appears. If they cancel at the auth prompt the scaffold
 * is left intact and they can run `quickback compile` later.
 *
 * Both `create` and `start` call this; the only difference is how the
 * options object is filled in.
 */
export async function runCreate(options) {
    // Resolve template
    const template = getTemplate(options.template);
    if (!template) {
        console.error(pc.red(`Unknown template: ${options.template}\n`));
        console.log(formatTemplateList());
        process.exit(1);
    }
    // Resolve target directory. `inPlace` means scaffold into cwd (the
    // `npx quickback start` in-an-empty-folder path); otherwise we create
    // a fresh subdirectory named after appName.
    const targetDir = options.inPlace
        ? resolve(process.cwd())
        : resolve(process.cwd(), options.appName);
    if (options.inPlace) {
        if (existsSync(join(targetDir, "quickback"))) {
            console.error(pc.red(`This folder already has a quickback/ directory.`));
            console.log(pc.gray("Run `quickback compile` to compile the existing project, or choose an empty folder."));
            process.exit(1);
        }
    }
    else if (existsSync(targetDir)) {
        console.error(pc.red(`Directory already exists: ${options.appName}`));
        console.log(pc.gray("Choose a different name or delete the existing directory."));
        process.exit(1);
    }
    console.log(pc.cyan(`Creating ${template.name}...`));
    console.log(pc.gray(`Template: ${options.template}`));
    console.log(pc.gray(`Location: ${targetDir}\n`));
    // Step 1: Create project directory
    const spinner = ora("Creating project structure...").start();
    const resolvedTemplateId = TEMPLATE_ALIASES[options.template] || options.template;
    const isSaas = resolvedTemplateId === "b2bsaas-cloudflare";
    const isBlog = resolvedTemplateId === "blog-cloudflare";
    const isTodos = resolvedTemplateId === "todos-cloudflare";
    // The default `betterauth-d1-cloudflare` (and any non-example template)
    // scaffolds NO example feature — users get a bare auth + DB scaffold
    // and add their own definitions. The todos example is now opt-in via
    // `quickback create todos <name>` so newcomers aren't forced to delete
    // sample code before writing their own. `empty-*` is preserved as the
    // explicit "no anything" path; the default path is now functionally
    // equivalent but keeps the auth+D1 framing in its description.
    const scaffoldsExample = isSaas || isBlog || isTodos;
    mkdirSync(targetDir, { recursive: true });
    mkdirSync(join(targetDir, "quickback"), { recursive: true });
    if (scaffoldsExample) {
        const featureName = isSaas ? "projects" : isBlog ? "posts" : "todos";
        mkdirSync(join(targetDir, "quickback", "features", featureName), { recursive: true });
    }
    else {
        mkdirSync(join(targetDir, "quickback", "features"), { recursive: true });
    }
    spinner.succeed("Project structure created");
    // Step 2: Generate config file
    spinner.start("Generating configuration...");
    generateConfigFile(targetDir, options.appName, template, resolvedTemplateId);
    if (isSaas) {
        generateProjectsFeature(targetDir);
    }
    else if (isBlog) {
        generateBlogFeature(targetDir);
    }
    else if (isTodos) {
        generateTodoFeature(targetDir);
    }
    generateAuthConfig(targetDir);
    spinner.succeed("Configuration generated");
    // Step 3: Setup Cloudflare resources (if applicable)
    //
    // Wrangler has its own auth, so this can run before the Quickback auth
    // gate. Users who haven't run `wrangler login` see a friendly warning
    // and the step is skipped — they fill in D1 IDs later.
    if (template.requiresCloudflare && !options.skipCloudflareSetup) {
        await setupCloudflareResources(options.appName, targetDir, options.verbose);
    }
    // Step 4: Auth gate — only now do we ask for credentials. The user has
    // seen real progress (folder created, config written, Cloudflare set
    // up); cancelling here leaves a usable scaffold on disk.
    const authResult = await ensureAuthForCompile({
        apiUrl: getApiUrl(),
        loginFn: login,
        reason: "Compiling needs an account; scaffolded files are already on disk and safe to keep.",
    });
    if (authResult.status === 'cancelled') {
        console.log(pc.yellow("\nScaffold complete, compile skipped (no auth)."));
        console.log(pc.gray("\nYour project is on disk at:"));
        console.log(pc.cyan(`  ${targetDir}`));
        console.log(pc.gray("\nWhen ready:"));
        console.log(pc.cyan(`  cd ${options.inPlace ? '.' : options.appName}`));
        console.log(pc.cyan("  quickback login"));
        console.log(pc.cyan("  quickback compile"));
        console.log("");
        return;
    }
    // Step 5: Compile the project via remote API
    spinner.start("Compiling via Quickback API...");
    const quickbackDir = join(targetDir, "quickback");
    const configPath = await findConfigPath(quickbackDir);
    if (!configPath) {
        spinner.fail("Configuration not found");
        throw new Error("Could not find quickback.config.ts");
    }
    const config = await loadConfig(configPath);
    const featuresDir = await findFeaturesDir(targetDir);
    const features = featuresDir ? await loadFeatures(featuresDir) : [];
    const result = await callCompiler({
        config: {
            name: config.name,
            preset: config.preset,
            template: config.template,
            providers: config.providers,
            build: config.build,
            features: config.features,
            // Forward top-level auth config (roleHierarchy, etc.)
            // — without this, `roles: ["member+"]` in templates fails
            // at compile time with "auth.roleHierarchy is not configured."
            auth: config.auth,
        },
        features: features.map((f) => ({
            name: f.name,
            schema: f.schema,
            tables: f.tables,
            resourceSource: f.resourceSource,
            actionFiles: f.actionFiles,
            featureLibFiles: f.featureLibFiles,
        })),
    }, (msg) => spinner.text = msg);
    // Write output files — partition runtime vs state artifacts
    const { runtimeFiles, stateFiles } = partitionGeneratedFiles(result.files);
    await writeFiles(targetDir, runtimeFiles);
    if (stateFiles.length > 0) {
        await writeFilesWithMerge(quickbackDir, stateFiles);
    }
    spinner.succeed(`Compiled ${result.meta.fileCount} files (v${result.meta.version})`);
    // Step 6: Install dependencies
    if (!options.skipInstall) {
        spinner.start("Installing dependencies...");
        const pm = detectPackageManager();
        const installResult = runCommand(pm, ["install"], targetDir, options.verbose);
        if (installResult.success) {
            spinner.succeed(`Dependencies installed with ${pm}`);
        }
        else {
            spinner.warn("Dependency installation had issues");
        }
    }
    // Step 7: Generate database schemas (drizzle-kit generate)
    //
    // Bare scaffolds (the default `cloudflare` template) ship with zero
    // features, so drizzle-kit's `./src/features/**/schema.ts` glob
    // matches nothing and the CLI explodes with "No schema files found".
    // Skip drizzle-kit entirely in that case; the compiler already emitted
    // the auth/files migrations alongside drizzle.auth.config.ts.
    if (!scaffoldsExample) {
        spinner.info("No features yet — run 'quickback compile' after adding one");
    }
    else {
        spinner.start("Generating database schemas...");
        const schemaResult = runCommand("npx", ["drizzle-kit", "generate"], targetDir, options.verbose);
        if (schemaResult.success) {
            spinner.succeed("Database schemas generated");
        }
        else {
            spinner.warn("Schema generation failed — run `npx drizzle-kit generate` to retry");
            if (options.verbose && schemaResult.stderr) {
                console.log(pc.gray(schemaResult.stderr));
            }
        }
    }
    // Step 8: Apply local migrations (for non-Cloudflare)
    if (!template.requiresCloudflare && !options.skipMigrations) {
        // Ensure data directory exists for SQLite
        const dataDir = join(targetDir, "data");
        if (!existsSync(dataDir)) {
            mkdirSync(dataDir, { recursive: true });
        }
        spinner.start("Applying local migrations...");
        const migrateResult = runCommand("npx", ["drizzle-kit", "migrate"], targetDir, options.verbose);
        if (migrateResult.success) {
            spinner.succeed("Local migrations applied");
        }
        else {
            spinner.warn("Migrations skipped");
        }
    }
    // Success!
    printSuccessMessage(options.appName, template, options.inPlace);
}
// ============================================================================
// Argument Parsing
// ============================================================================
async function parseCreateArgs(args) {
    let template;
    let appName;
    let skipCloudflareSetup = false;
    let skipInstall = false;
    let skipMigrations = false;
    let dryRun = false;
    let verbose = false;
    // Parse flags and positional args
    const positional = [];
    for (const arg of args) {
        if (arg === "--skip-cloudflare-setup" || arg === "--skip-cf") {
            skipCloudflareSetup = true;
        }
        else if (arg === "--skip-install") {
            skipInstall = true;
        }
        else if (arg === "--skip-migrations") {
            skipMigrations = true;
        }
        else if (arg === "--dry-run") {
            dryRun = true;
        }
        else if (arg === "--verbose" || arg === "-v") {
            verbose = true;
        }
        else if (arg === "--help" || arg === "-h") {
            printHelp();
            process.exit(0);
        }
        else if (arg === "--list" || arg === "-l") {
            console.log(formatTemplateList());
            process.exit(0);
        }
        else if (!arg.startsWith("-")) {
            positional.push(arg);
        }
    }
    // Extract template and appName from positional args
    if (positional.length >= 2) {
        template = positional[0];
        appName = positional[1];
    }
    else if (positional.length === 1) {
        // Could be template or app name - check if it's a known template
        if (getTemplate(positional[0]) || TEMPLATE_ALIASES[positional[0]]) {
            template = positional[0];
        }
        else {
            appName = positional[0];
        }
    }
    // Interactive prompts if needed
    if (!template) {
        const templates = listTemplates();
        const response = await prompts({
            type: "select",
            name: "template",
            message: "Choose a template",
            choices: templates.map(t => ({
                title: t.name,
                description: t.description,
                value: Object.entries(TEMPLATE_ALIASES).find(([_, v]) => v === Object.keys(TEMPLATE_ALIASES).find(k => getTemplate(k)?.name === t.name))?.[0] || t.name.toLowerCase().replace(/\s+/g, "-"),
            })),
        });
        template = response.template;
        if (!template) {
            console.log(pc.yellow("\nOperation cancelled."));
            process.exit(0);
        }
    }
    if (!appName) {
        const response = await prompts({
            type: "text",
            name: "appName",
            message: "Project name",
            validate: (v) => {
                if (!v || v.trim().length === 0)
                    return "Name is required";
                if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(v)) {
                    return "Use lowercase letters, numbers, and hyphens only";
                }
                return true;
            },
        });
        appName = response.appName;
        if (!appName) {
            console.log(pc.yellow("\nOperation cancelled."));
            process.exit(0);
        }
    }
    return {
        template: template,
        appName: appName,
        skipCloudflareSetup,
        skipInstall,
        skipMigrations,
        dryRun,
        verbose,
    };
}
// ============================================================================
// File Generation
// ============================================================================
function isSingleTenantTemplate(templateId) {
    return templateId === 'blog-cloudflare';
}
function generateConfigFile(targetDir, appName, template, templateId) {
    const providers = getProvidersForPreset(template.preset, templateId);
    const singleTenant = isSingleTenantTemplate(templateId);
    const configContent = `/**
 * Quickback Configuration
 * Generated by: quickback create ${template.preset} ${appName}
 *
 * Project structure:
 *   ${appName}/
 *   ├── quickback/              ← definitions live here
 *   │   ├── quickback.config.ts
 *   │   └── features/...
 *   ├── src/                    ← compiled code
 *   └── package.json
 *
 * Run 'quickback compile' to regenerate after editing definitions.
 */

export default {
    name: "${appName}",
    template: "hono",
    features: {
        organizations: ${singleTenant ? 'false' : 'true'},
    },${singleTenant ? '' : `
    auth: {
        // Role hierarchy: lowest → highest privilege.
        // Use "role+" in access rules to mean "this role and above".
        // e.g. roles: ["member+"] expands to ["member", "admin", "owner"].
        roleHierarchy: ["member", "admin", "owner"],
    },`}
    providers: ${JSON.stringify(providers, null, 8).replace(/^/gm, '    ').trim()},
};
`;
    writeFileSync(join(targetDir, "quickback", "quickback.config.ts"), configContent);
}
function getProvidersForPreset(preset, templateId) {
    // SaaS template gets extra providers (file storage, split databases, webhooks)
    if (templateId === "b2bsaas-cloudflare") {
        return {
            runtime: { name: "cloudflare", config: {} },
            database: {
                name: "cloudflare-d1",
                config: {
                    splitDatabases: true,
                    authBinding: "AUTH_DB",
                    featuresBinding: "DB",
                    webhooksBinding: "WEBHOOKS_DB",
                    realtime: true,
                },
            },
            auth: { name: "better-auth", config: {} },
            fileStorage: {
                name: "cloudflare-r2",
                config: {},
            },
        };
    }
    switch (preset) {
        case "blog":
            return {
                runtime: { name: "cloudflare", config: {} },
                database: { name: "cloudflare-d1", config: { binding: "DB" } },
                // Single-tenant: enable Better Auth's admin plugin so the
                // ADMIN pseudo-role resolves (the blog gates writes on it).
                auth: { name: "better-auth", config: { plugins: ["admin"] } },
            };
        case "cloudflare":
            return {
                runtime: { name: "cloudflare", config: {} },
                database: { name: "cloudflare-d1", config: { binding: "DB", realtime: true } },
                auth: { name: "better-auth", config: {} },
            };
        default:
            // Default to cloudflare. The bun preset case is intentionally
            // gone — see apps/compiler/src/providers/runtime/providers/bun/
            // README.md → Known Issues for why.
            return {
                runtime: { name: "cloudflare", config: {} },
                database: { name: "cloudflare-d1", config: { binding: "DB" } },
                auth: { name: "better-auth", config: {} },
            };
    }
}
function generateTodoFeature(targetDir) {
    // Audit fields (createdAt, createdBy, modifiedAt, modifiedBy) and soft delete (deletedAt)
    // are added automatically by the compiler based on firewall config — do not define them here.
    const todosContent = `/**
 * Todo Feature
 *
 * Audit fields (createdAt, createdBy, modifiedAt, modifiedBy) and soft delete
 * (deletedAt) are added automatically by the compiler — no need to define them.
 */
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { defineTable } from "@quickback/compiler";

export const todos = sqliteTable("todos", {
    id: text("id").primaryKey(),
    title: text("title").notNull(),
    description: text("description"),
    completed: integer("completed", { mode: "boolean" }).default(false),
    priority: text("priority").default("medium"),
    dueDate: integer("due_date", { mode: "timestamp" }),

    // Ownership — used by Firewall for data isolation.
    // userId is the firewall convention for the row's owner; ownerId is
    // reserved for business logic. See compiler RLS column semantics.
    organizationId: text("organization_id").notNull(),
    userId: text("user_id").notNull(),
});

export default defineTable(todos, {
    firewall: {
        organization: {},
        owner: {},
        softDelete: {},
    },

    guards: {
        createable: ["title", "description", "completed", "priority", "dueDate"],
        updatable: ["title", "description", "completed", "priority", "dueDate"],
        immutable: ["id", "organizationId", "userId"],
    },

    masking: {
        description: {
            type: "redact",
            show: { roles: ["admin+"], or: "owner" },
        },
    },

    read: {
        access: { roles: ["member+"] },

        // Named views = field projections with their own endpoint.
        // \`dashboard\` strips the description (heavy + masked) and
        // returns just what a list UI needs — generates a separate
        // endpoint at GET /api/v1/todos/views/dashboard. All security
        // pillars (firewall, masking, access) still apply.
        views: {
            dashboard: {
                fields: ["id", "title", "completed", "priority", "dueDate"],
                access: { roles: ["member+"] },
            },
        },
    },

    create: { access: { roles: ["member+"] } },
    update: { access: { roles: ["member+"] } },
    delete: { access: { roles: ["admin+"] }, mode: "soft" },
});
`;
    const completeActionContent = `import { z } from "zod";
import { eq } from "drizzle-orm";
import { defineAction } from "../.quickback/define-action";
import { todos } from "../todos";

export default defineAction({
    description: "Mark todo as complete",
    input: z.object({
        completedAt: z.string().datetime().optional(),
    }),
    access: {
        roles: ["member+"],
        record: { completed: { equals: false } },
    },
    sideEffects: "sync",
    async execute({ db, record, input }) {
        const [updated] = await db
            .update(todos)
            .set({ completed: true })
            .where(eq(todos.id, record.id))
            .returning();
        return updated;
    },
});
`;
    const uncompleteActionContent = `import { z } from "zod";
import { eq } from "drizzle-orm";
import { defineAction } from "../.quickback/define-action";
import { todos } from "../todos";

export default defineAction({
    description: "Mark todo as incomplete",
    input: z.object({}),
    access: {
        roles: ["member+"],
        record: { completed: { equals: true } },
    },
    sideEffects: "sync",
    async execute({ db, record }) {
        const [updated] = await db
            .update(todos)
            .set({ completed: false })
            .where(eq(todos.id, record.id))
            .returning();
        return updated;
    },
});
`;
    const featureDir = join(targetDir, "quickback", "features", "todos");
    const actionsDir = join(featureDir, "actions");
    mkdirSync(actionsDir, { recursive: true });
    writeFileSync(join(featureDir, "todos.ts"), todosContent);
    writeFileSync(join(actionsDir, "complete.ts"), completeActionContent);
    writeFileSync(join(actionsDir, "uncomplete.ts"), uncompleteActionContent);
}
function generateBlogFeature(targetDir) {
    const postsContent = `/**
 * Posts Feature (Single-Tenant Blog)
 *
 * Public read access, admin-only write access.
 * Uses owner scope for tracking who created each post.
 * Audit fields are added automatically by the compiler.
 */
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { defineTable } from "@quickback/compiler";

export const posts = sqliteTable("posts", {
    id: text("id").primaryKey(),
    title: text("title").notNull(),
    slug: text("slug").notNull().unique(),
    content: text("content"),
    excerpt: text("excerpt"),
    status: text("status").notNull().default("draft"),
    publishedAt: text("published_at"),

    // Author tracking only — this is metadata, not access control.
    // Named "authorId" (not "userId") so the firewall validator does NOT
    // enter user-scoped RLS mode. Reads are gated by PUBLIC; writes by the
    // admin access check below.
    authorId: text("author_id").notNull(),
});

export default defineTable(posts, {
    // PUBLIC reads — no isolation predicate at the firewall layer.
    // Anonymous viewers have no ctx.userId, so a userId/orgId WHERE clause
    // would gate every row out. softDelete keeps deleted posts hidden.
    firewall: {
        softDelete: {},
    },

    guards: {
        createable: ["title", "slug", "content", "excerpt", "status", "publishedAt"],
        updatable: ["title", "slug", "content", "excerpt", "status", "publishedAt"],
        immutable: ["id", "authorId"],
    },

    read: {
        access: { roles: ["PUBLIC"] },
    },

    // Single-tenant — uses Better Auth's admin plugin role (ADMIN
    // pseudo-role), not the org-plugin hierarchy. Multi-tenant
    // templates expand "admin+" via auth.roleHierarchy instead.
    create: { access: { roles: ["ADMIN"] } },
    update: { access: { roles: ["ADMIN"] } },
    delete: { access: { roles: ["ADMIN"] }, mode: "soft" },
});
`;
    const publishActionContent = `import { z } from "zod";
import { eq } from "drizzle-orm";
import { defineAction } from "../.quickback/define-action";
import { posts } from "../posts";

export default defineAction({
    description: "Publish a draft post",
    input: z.object({}),
    access: {
        roles: ["admin"],
        record: { status: { equals: "draft" } },
    },
    sideEffects: "sync",
    async execute({ db, record }) {
        const [updated] = await db
            .update(posts)
            .set({
                status: "published",
                publishedAt: new Date().toISOString(),
            })
            .where(eq(posts.id, record.id))
            .returning();
        return updated;
    },
});
`;
    const unpublishActionContent = `import { z } from "zod";
import { eq } from "drizzle-orm";
import { defineAction } from "../.quickback/define-action";
import { posts } from "../posts";

export default defineAction({
    description: "Revert a published post to draft",
    input: z.object({}),
    access: {
        roles: ["admin"],
        record: { status: { equals: "published" } },
    },
    sideEffects: "sync",
    async execute({ db, record }) {
        const [updated] = await db
            .update(posts)
            .set({
                status: "draft",
                publishedAt: null,
            })
            .where(eq(posts.id, record.id))
            .returning();
        return updated;
    },
});
`;
    const featureDir = join(targetDir, "quickback", "features", "posts");
    const actionsDir = join(featureDir, "actions");
    mkdirSync(actionsDir, { recursive: true });
    writeFileSync(join(featureDir, "posts.ts"), postsContent);
    writeFileSync(join(actionsDir, "publish.ts"), publishActionContent);
    writeFileSync(join(actionsDir, "unpublish.ts"), unpublishActionContent);
}
function generateProjectsFeature(targetDir) {
    // SaaS template uses a richer "projects" feature with views
    // Audit fields and soft delete (deletedAt) are added automatically by the compiler.
    const projectsContent = `/**
 * Projects Feature
 * B2B SaaS example with views.
 *
 * Audit fields (createdAt, createdBy, modifiedAt, modifiedBy) and soft delete
 * (deletedAt) are added automatically by the compiler — no need to define them.
 */
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { defineTable } from "@quickback/compiler";

export const projects = sqliteTable("projects", {
    id: text("id").primaryKey(),
    name: text("name").notNull(),
    description: text("description"),
    status: text("status").default("active"),

    // Ownership — used by Firewall for data isolation.
    // userId is the firewall convention for the row's owner; ownerId is
    // reserved for business logic. See compiler RLS column semantics.
    organizationId: text("organization_id").notNull(),
    userId: text("user_id").notNull(),
});

export default defineTable(projects, {
    firewall: {
        organization: {},
        owner: {},
        softDelete: {},
    },
    guards: {
        createable: ["name", "description", "status"],
        updatable: ["name", "description", "status"],
        immutable: ["id", "organizationId", "userId"],
    },
    read: {
        access: { roles: ["member+"] },
        views: {
            summary: {
                fields: ["id", "name", "status"],
                access: { roles: ["member+"] },
            },
        },
    },
    create: { access: { roles: ["member+"] } },
    update: { access: { roles: ["member+"] } },
    delete: { access: { roles: ["admin+"] }, mode: "soft" },
});
`;
    const featureDir = join(targetDir, "quickback", "features", "projects");
    writeFileSync(join(featureDir, "projects.ts"), projectsContent);
}
function generateAuthConfig(targetDir) {
    const authContent = `/**
 * Better Auth Configuration
 *
 * Security Best Practices:
 * - Cookie security: SameSite=Lax (CSRF protection), Secure (HTTPS only), HttpOnly (XSS protection)
 * - Rate limiting: Device authorization endpoints protected against brute-force
 * - Trusted origins: Set TRUSTED_ORIGINS env var to whitelist allowed domains
 */
export default {
    emailAndPassword: {
        enabled: true,
        requireEmailVerification: false,
    },
    plugins: ["anonymous"],
    session: {
        expiresInDays: 30,
        updateAgeInDays: 1,
    },
    database: {
        usePlural: true,
        debugLogs: true,
    },
    // Security: Secure cookie defaults
    advanced: {
        defaultCookieAttributes: {
            sameSite: 'lax',   // Prevent CSRF attacks
            secure: true,       // HTTPS only
            httpOnly: true,     // Prevent JavaScript access (XSS mitigation)
        },
    },
    // Security: Rate limiting for device authorization
    rateLimit: {
        enabled: true,
        window: 60,
        max: 100,
        customRules: {
            "/sign-in/email": { window: 60, max: 10 },
            "/sign-in/social": { window: 60, max: 10 },
            "/device/init": { window: 60, max: 5 },
            "/device/poll": { window: 60, max: 30 },
            "/device/token": { window: 60, max: 10 },
        },
    },
};
`;
    mkdirSync(join(targetDir, "quickback", "definitions", "auth", "better-auth"), { recursive: true });
    writeFileSync(join(targetDir, "quickback", "definitions", "auth", "better-auth", "config.ts"), authContent);
}
// ============================================================================
// Cloudflare Setup
// ============================================================================
async function setupCloudflareResources(appName, targetDir, verbose) {
    const spinner = ora("Setting up Cloudflare resources...").start();
    // Check if wrangler is available
    const wranglerCheck = spawnSync("npx", ["wrangler", "--version"], { encoding: "utf-8" });
    if (wranglerCheck.status !== 0) {
        spinner.warn("Wrangler not available - skipping Cloudflare setup");
        console.log(pc.gray("  Install wrangler and run: npx wrangler d1 create " + appName + "-db"));
        return;
    }
    // Check authentication
    const whoami = spawnSync("npx", ["wrangler", "whoami"], { encoding: "utf-8" });
    if (whoami.status !== 0 || whoami.stdout.includes("not authenticated")) {
        spinner.warn("Not authenticated with Cloudflare - skipping resource creation");
        console.log(pc.gray("  Run 'npx wrangler login' first, then create resources manually."));
        return;
    }
    // Create D1 database
    spinner.text = `Creating D1 database: ${appName}-db`;
    const d1Result = spawnSync("npx", ["wrangler", "d1", "create", `${appName}-db`], { encoding: "utf-8", cwd: targetDir });
    if (d1Result.status === 0) {
        // Extract database ID from output
        const dbIdMatch = d1Result.stdout.match(/database_id\s*=\s*"([^"]+)"/);
        if (dbIdMatch) {
            spinner.succeed(`D1 database created: ${appName}-db (${dbIdMatch[1]})`);
        }
        else {
            spinner.succeed(`D1 database created: ${appName}-db`);
        }
    }
    else if (d1Result.stderr.includes("already exists")) {
        spinner.info(`D1 database already exists: ${appName}-db`);
    }
    else {
        spinner.warn("D1 database creation failed");
        if (verbose) {
            console.log(pc.gray(d1Result.stderr));
        }
    }
}
// ============================================================================
// Utilities
// ============================================================================
function runCommand(cmd, args, cwd, verbose) {
    const result = spawnSync(cmd, args, {
        cwd,
        encoding: "utf-8",
        stdio: verbose ? "inherit" : "pipe",
    });
    return {
        success: result.status === 0,
        stdout: result.stdout || "",
        stderr: result.stderr || "",
    };
}
function detectPackageManager() {
    // Honor an existing lockfile in cwd if one happens to be there.
    if (existsSync("bun.lockb"))
        return "bun";
    if (existsSync("pnpm-lock.yaml"))
        return "pnpm";
    if (existsSync("yarn.lock"))
        return "yarn";
    return "npm";
}
function performDryRun(options) {
    const template = getTemplate(options.template);
    console.log(pc.bold("Dry Run - No changes will be made\n"));
    console.log(pc.cyan("Configuration:"));
    console.log(`  Template:     ${options.template}${template ? ` (${template.name})` : ""}`);
    console.log(`  App Name:     ${options.appName}`);
    console.log(`  Target Dir:   ${resolve(process.cwd(), options.appName)}`);
    console.log("");
    console.log(pc.cyan("Steps that would be executed:"));
    console.log("  1. Create project directory structure");
    console.log("  2. Generate quickback.config.ts");
    console.log("  3. Generate feature definitions");
    if (template?.requiresCloudflare && !options.skipCloudflareSetup) {
        console.log("  4. Create Cloudflare D1 database");
    }
    else {
        console.log("  4. [SKIP] Cloudflare setup");
    }
    console.log("  5. Compile project (quickback compile)");
    if (!options.skipInstall) {
        console.log("  6. Install dependencies");
    }
    else {
        console.log("  6. [SKIP] Install dependencies");
    }
    console.log("  7. Generate database schemas");
    if (!template?.requiresCloudflare && !options.skipMigrations) {
        console.log("  8. Apply local migrations");
    }
    else {
        console.log("  8. [SKIP] Local migrations");
    }
    console.log(pc.green("\nDry run complete. Remove --dry-run to execute."));
}
function printSuccessMessage(appName, template, inPlace) {
    console.log("");
    console.log(pc.green("✔ Project created successfully!"));
    console.log("");
    console.log(pc.bold("Next steps:"));
    if (!inPlace) {
        console.log(pc.cyan(`  cd ${appName}`));
    }
    console.log(pc.cyan("  npm run dev"));
    console.log("");
    if (template.requiresCloudflare) {
        console.log(pc.bold("Deploy to Cloudflare:"));
        console.log(pc.cyan("  npm run deploy"));
        console.log("");
    }
    console.log(pc.bold("Useful commands:"));
    console.log("  npm run dev              " + pc.gray("Start development server"));
    console.log("  quickback compile        " + pc.gray("Regenerate after editing definitions"));
    console.log("  npx drizzle-kit generate " + pc.gray("Generate new migrations"));
    console.log("  npx drizzle-kit migrate  " + pc.gray("Apply database migrations"));
    if (template.requiresCloudflare) {
        console.log("  npm run deploy           " + pc.gray("Deploy to Cloudflare Workers"));
    }
    console.log("");
    console.log(pc.bold("Project structure:"));
    console.log(pc.gray(`  ${inPlace ? "." : appName}/`));
    console.log(pc.gray("  ├── quickback/           ← edit definitions here"));
    console.log(pc.gray("  ├── src/                 ← generated API code"));
    console.log(pc.gray("  └── quickback/drizzle/   ← database migrations"));
    console.log("");
}
function printHelp() {
    console.log(`
${pc.bold("quickback create")} - Create a new Quickback project

${pc.bold("Usage:")}
  quickback create <template> <app-name>
  quickback create <app-name>              ${pc.gray("(interactive template selection)")}

${pc.bold("Examples:")}
  quickback create betterauth-d1-cloudflare my-app
  quickback create cloudflare my-app       ${pc.gray("(alias)")}
  quickback create todos my-app            ${pc.gray("(working example)")}

${pc.bold("Options:")}
  --skip-cloudflare-setup   Skip automatic Cloudflare resource creation
  --skip-install            Skip npm/bun install
  --skip-migrations         Skip database migrations
  --dry-run                 Preview what would be created
  --verbose, -v             Show detailed output
  --list, -l                List available templates
  --help, -h                Show this help

${pc.bold("Templates:")}
${formatTemplateList()}
`);
}
//# sourceMappingURL=create.js.map