import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { parseEnvExample, StructuredError } from '@agentuity/core';
import { createQueue, createResources, listOrganizations, listQueues, listResources, projectCreate, validateDatabaseName, } from '@agentuity/server';
import { isTTY } from '../../auth';
import { createProjectConfig, getCatalystAPIClient, getGlobalCatalystAPIClient, } from '../../config';
import { addResourceEnvVars } from '../../env-util';
import { getDefaultBranch, isGitAvailable } from '../../git-helper';
import { fetchRegionsWithCache } from '../../regions';
import * as tui from '../../tui';
import { createPrompt } from '../../tui';
import { checkGithubRepo, createGithubRepo, getGithubBotIdentity, getGithubToken, linkProjectToRepo, } from '../git/api';
import { initGitRepo } from './download';
// ─── Structured Errors ───
const RemoteImportInvalidURLError = StructuredError('RemoteImportInvalidURLError');
const RemoteImportUnsupportedHostError = StructuredError('RemoteImportUnsupportedHostError');
const RemoteImportDownloadError = StructuredError('RemoteImportDownloadError');
const RemoteImportExtractError = StructuredError('RemoteImportExtractError');
const RemoteImportNoOrganizationError = StructuredError('RemoteImportNoOrganizationError', 'No organizations found for your account');
const RemoteImportNoRegionError = StructuredError('RemoteImportNoRegionError', 'No cloud regions available');
const RemoteImportInvalidRepoError = StructuredError('RemoteImportInvalidRepoError');
const RemoteImportGitError = StructuredError('RemoteImportGitError');
const RemoteImportDeployError = StructuredError('RemoteImportDeployError');
const RemoteImportDirectoryNotFoundError = StructuredError('RemoteImportDirectoryNotFoundError');
const RemoteImportConfigError = StructuredError('RemoteImportConfigError');
/**
 * Sanitize a string by removing any embedded GitHub tokens from URLs.
 * Prevents token leakage in error messages and logs.
 */
function sanitizeTokens(msg) {
    return msg.replace(/x-access-token:[^@]+@/g, 'x-access-token:***@');
}
/**
 * Build GitHub API request headers, using the Agentuity-managed GitHub token
 * when available, falling back to the GITHUB_TOKEN env var.
 */
async function githubHeaders(apiClient) {
    const headers = {
        Accept: 'application/vnd.github+json',
        'User-Agent': 'Agentuity-CLI',
    };
    try {
        const { token } = await getGithubToken(apiClient);
        headers.Authorization = `Bearer ${token}`;
    }
    catch {
        // Fallback to GITHUB_TOKEN env var
        const githubToken = process.env.GITHUB_TOKEN;
        if (githubToken) {
            headers.Authorization = `Bearer ${githubToken}`;
        }
    }
    return headers;
}
/**
 * Fetch the default branch of a GitHub repository via the API.
 * Falls back to 'main' on any error.
 */
async function fetchDefaultBranch(owner, repo, apiClient) {
    try {
        const headers = await githubHeaders(apiClient);
        const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
            headers,
        });
        if (!resp.ok)
            return 'main';
        const data = (await resp.json());
        return data.default_branch ?? 'main';
    }
    catch {
        return 'main';
    }
}
/**
 * Parse a GitHub URL into its components.
 *
 * Supported formats:
 *   https://github.com/owner/repo
 *   https://github.com/owner/repo/tree/branch
 *   https://github.com/owner/repo/tree/branch/path/to/dir
 *
 * When the URL does not include a branch (no `/tree/…` segment), the GitHub
 * API is queried to discover the repository's default branch.
 */
export async function parseGitHubUrl(url, apiClient) {
    let parsed;
    try {
        parsed = new URL(url);
    }
    catch {
        throw new RemoteImportInvalidURLError({ message: `Invalid URL: ${url}` });
    }
    if (parsed.hostname !== 'github.com') {
        throw new RemoteImportUnsupportedHostError({
            message: `Only GitHub URLs are supported. Got: ${parsed.hostname}`,
        });
    }
    // pathname is like /owner/repo or /owner/repo/tree/branch/path
    const parts = parsed.pathname.replace(/^\//, '').replace(/\/$/, '').split('/');
    if (parts.length < 2) {
        throw new RemoteImportInvalidURLError({
            message: `Invalid GitHub URL: expected at least owner/repo in path. Got: ${parsed.pathname}`,
        });
    }
    const owner = parts[0];
    // Strip .git suffix from repo name if present
    const repo = parts[1].replace(/\.git$/, '');
    let branch;
    let directory;
    // /owner/repo/tree/branch[/path/to/dir]
    if (parts.length >= 4 && parts[2] === 'tree') {
        branch = parts[3];
        if (parts.length > 4) {
            directory = parts.slice(4).join('/');
        }
    }
    else {
        // No branch in URL — ask GitHub for the repo's default branch
        branch = await fetchDefaultBranch(owner, repo, apiClient);
    }
    return { owner, repo, branch, directory };
}
/**
 * Download and extract a GitHub repository zipball to a temp directory.
 * Returns the path to the extracted content root.
 */
async function downloadAndExtract(parsed, apiClient, logger) {
    const { owner, repo, branch } = parsed;
    const zipUrl = `https://api.github.com/repos/${owner}/${repo}/zipball/${branch}`;
    logger.debug('[remote-import] Downloading zipball from: %s', zipUrl);
    const tempDir = mkdtempSync(join(tmpdir(), 'agentuity-remote-'));
    try {
        const zipPath = join(tempDir, 'download.zip');
        // Download the zipball
        const headers = await githubHeaders(apiClient);
        await tui.spinner({
            message: `Downloading ${owner}/${repo}...`,
            clearOnSuccess: true,
            callback: async () => {
                const resp = await fetch(zipUrl, {
                    headers,
                    redirect: 'follow',
                });
                if (!resp.ok) {
                    throw new RemoteImportDownloadError({
                        message: `Failed to download from GitHub: ${resp.status} ${resp.statusText}`,
                    });
                }
                const buffer = Buffer.from(await resp.arrayBuffer());
                await Bun.write(zipPath, buffer);
                logger.debug('[remote-import] Downloaded %d bytes to %s', buffer.length, zipPath);
                return resp;
            },
        });
        // Extract the zip
        const extractDir = join(tempDir, 'extracted');
        mkdirSync(extractDir, { recursive: true });
        await tui.spinner({
            message: 'Extracting template...',
            clearOnSuccess: true,
            callback: async () => {
                // Use Bun's built-in unzip via subprocess
                const proc = Bun.spawnSync(['unzip', '-q', '-o', zipPath, '-d', extractDir], {
                    stdout: 'pipe',
                    stderr: 'pipe',
                });
                if (proc.exitCode !== 0) {
                    const stderr = proc.stderr.toString();
                    throw new RemoteImportExtractError({
                        message: `Failed to extract zip: ${stderr}`,
                    });
                }
                logger.debug('[remote-import] Extracted to %s', extractDir);
            },
        });
        // GitHub zipball creates a top-level directory like "owner-repo-sha/"
        // We need to find it and return its path
        const entries = readdirSync(extractDir);
        if (entries.length === 1 && entries[0]) {
            const innerDir = join(extractDir, entries[0]);
            return { extractDir: innerDir, tempDir };
        }
        return { extractDir, tempDir };
    }
    catch (err) {
        // Clean up temp directory on failure to prevent leaks
        try {
            rmSync(tempDir, { recursive: true, force: true });
        }
        catch {
            // Ignore cleanup errors
        }
        throw err;
    }
}
/**
 * Look for agentuity.yaml in the extracted content and parse it.
 * Returns the parsed content or null if not found.
 */
async function findAgentuityYaml(dir, logger) {
    const yamlPath = join(dir, 'agentuity.yaml');
    const file = Bun.file(yamlPath);
    if (!(await file.exists())) {
        logger.debug('[remote-import] No agentuity.yaml found at %s', yamlPath);
        return null;
    }
    try {
        const { YAML } = await import('bun');
        const content = await file.text();
        const parsed = YAML.parse(content);
        logger.debug('[remote-import] Parsed agentuity.yaml: %o', parsed);
        return parsed;
    }
    catch (err) {
        logger.debug('[remote-import] Failed to parse agentuity.yaml: %o', err);
        return null;
    }
}
/**
 * Create a project via the API in non-interactive mode using the provided name.
 */
async function createProjectNonInteractive(apiClient, config, logger, name, region, orgOverride) {
    // Fetch orgs — use the first one in non-interactive mode
    const orgs = await listOrganizations(apiClient);
    if (orgs.length === 0) {
        throw new RemoteImportNoOrganizationError();
    }
    const firstOrg = orgs[0];
    if (!firstOrg) {
        throw new RemoteImportNoOrganizationError();
    }
    const orgId = orgOverride ?? config.preferences?.orgId ?? firstOrg.id;
    // Determine region
    let selectedRegion = region;
    if (!selectedRegion) {
        selectedRegion = process.env.AGENTUITY_REGION ?? config.preferences?.region;
    }
    if (!selectedRegion) {
        const regions = await fetchRegionsWithCache(config.name, apiClient, logger);
        const firstRegion = regions[0];
        if (!firstRegion) {
            throw new RemoteImportNoRegionError();
        }
        selectedRegion = firstRegion.region;
    }
    const newProject = await tui.spinner({
        message: 'Creating project...',
        clearOnSuccess: true,
        callback: async () => {
            return projectCreate(apiClient, {
                name,
                orgId,
                cloudRegion: selectedRegion,
            });
        },
    });
    return {
        id: newProject.id,
        sdkKey: newProject.sdkKey,
        orgId,
        region: selectedRegion,
    };
}
/**
 * Create a project interactively — select org, region, name via TUI prompts.
 */
async function createProjectInteractive(apiClient, config, logger, defaultName) {
    // Fetch orgs
    const orgs = await tui.spinner({
        message: 'Fetching organizations...',
        clearOnSuccess: true,
        callback: () => listOrganizations(apiClient),
    });
    if (orgs.length === 0) {
        throw new RemoteImportNoOrganizationError();
    }
    // Select org
    const orgId = await tui.selectOrganization(orgs, config.preferences?.orgId);
    // Fetch and select region
    const regions = await tui.spinner({
        message: 'Fetching regions...',
        clearOnSuccess: true,
        callback: () => fetchRegionsWithCache(config.name, apiClient, logger),
    });
    let selectedRegion;
    if (regions.length === 1 && regions[0]) {
        selectedRegion = regions[0].region;
    }
    else {
        const prompt = tui.createPrompt();
        const options = regions.map((r) => ({
            value: r.region,
            label: `${r.description} (${r.region})`,
        }));
        const firstOption = options[0];
        selectedRegion = await prompt.select({
            message: 'Select a region:',
            options,
            initial: firstOption?.value ?? '',
        });
    }
    // Get project name
    const prompt = tui.createPrompt();
    const projectName = await prompt.text({
        message: 'Project name:',
        initial: defaultName,
        validate: (value) => {
            if (!value || value.trim().length === 0) {
                return 'Project name is required';
            }
            return true;
        },
    });
    // Create the project
    const newProject = await tui.spinner({
        message: 'Registering project...',
        clearOnSuccess: true,
        callback: async () => {
            return projectCreate(apiClient, {
                name: projectName,
                orgId,
                cloudRegion: selectedRegion,
            });
        },
    });
    return {
        id: newProject.id,
        sdkKey: newProject.sdkKey,
        orgId,
        region: selectedRegion,
    };
}
/**
 * Parse a repo URL or "owner/name" string into owner and name components.
 * Supports:
 *   - https://github.com/owner/repo
 *   - https://github.com/owner/repo.git
 *   - owner/repo
 */
function parseRepoTarget(repo) {
    // Try as a URL first
    try {
        const parsed = new URL(repo);
        if (parsed.hostname === 'github.com') {
            const parts = parsed.pathname.replace(/^\//, '').replace(/\/$/, '').split('/');
            if (parts.length >= 2 && parts[0] && parts[1]) {
                return { owner: parts[0], name: parts[1].replace(/\.git$/, '') };
            }
        }
    }
    catch {
        // Not a URL — try "owner/name" format
    }
    const parts = repo.split('/');
    if (parts.length === 2 && parts[0] && parts[1]) {
        return { owner: parts[0], name: parts[1].replace(/\.git$/, '') };
    }
    throw new RemoteImportInvalidRepoError({
        message: `Invalid repo target: "${repo}". Expected a GitHub URL (https://github.com/owner/repo) or owner/repo format.`,
    });
}
/**
 * Create a GitHub repo via the API. Preflight check already verified it doesn't exist.
 * Returns the HTTPS URL of the repo.
 */
async function createGithubRepoForImport(apiClient, repo, _logger) {
    const { owner, name } = parseRepoTarget(repo);
    const createResult = await tui.spinner({
        message: `Creating repository ${owner}/${name}...`,
        clearOnSuccess: true,
        callback: () => createGithubRepo(apiClient, {
            name,
            owner,
            private: true,
        }),
    });
    tui.success(`Created repository ${createResult.fullName}`);
    return createResult.url;
}
/**
 * Push the working directory to a remote git repository.
 */
async function pushToRepo(dest, repoUrl, apiClient, logger) {
    const gitAvailable = await isGitAvailable();
    if (!gitAvailable) {
        tui.warning('Git is not available — skipping git push.');
        return;
    }
    const defaultBranch = (await getDefaultBranch()) || 'main';
    // Get GitHub token from Agentuity API (uses stored OAuth token)
    let remoteUrl = repoUrl;
    try {
        const { token } = await getGithubToken(apiClient);
        const parsed = new URL(repoUrl);
        if (parsed.hostname === 'github.com') {
            remoteUrl = `https://x-access-token:${token}@github.com${parsed.pathname}`;
            if (!remoteUrl.endsWith('.git')) {
                remoteUrl += '.git';
            }
        }
    }
    catch (err) {
        logger.debug('[remote-import] Could not get GitHub token from API, trying without auth: %o', err);
        // Fall through — push will likely fail for private repos but may work for public
    }
    await tui.spinner({
        message: 'Pushing to remote repository...',
        clearOnSuccess: true,
        callback: async () => {
            // Add remote origin
            const addRemote = Bun.spawnSync(['git', 'remote', 'add', 'origin', remoteUrl], {
                cwd: dest,
                stdout: 'pipe',
                stderr: 'pipe',
            });
            if (addRemote.exitCode !== 0) {
                // Remote might already exist, try set-url instead
                const setUrl = Bun.spawnSync(['git', 'remote', 'set-url', 'origin', remoteUrl], {
                    cwd: dest,
                    stdout: 'pipe',
                    stderr: 'pipe',
                });
                if (setUrl.exitCode !== 0) {
                    throw new RemoteImportGitError({
                        message: `Failed to set git remote: ${sanitizeTokens(setUrl.stderr.toString())}`,
                    });
                }
            }
            // Push to remote
            const push = Bun.spawnSync(['git', 'push', '-u', 'origin', defaultBranch], {
                cwd: dest,
                stdout: 'pipe',
                stderr: 'pipe',
            });
            if (push.exitCode !== 0) {
                throw new RemoteImportGitError({
                    message: `Failed to push to remote: ${sanitizeTokens(push.stderr.toString())}`,
                });
            }
            logger.debug('[remote-import] Pushed to %s on branch %s', repoUrl, defaultBranch);
        },
    });
    tui.success(`Pushed to ${repoUrl}`);
}
/**
 * Run the deploy command as a subprocess.
 *
 * Uses `bunx agentuity deploy …` to match the fork-wrapper pattern used
 * elsewhere in the CLI (see deploy-fork.ts). This avoids relying on
 * `agentuity` being independently available on PATH.
 */
async function runDeploy(dest, logger) {
    tui.info('Deploying project...');
    const args = ['bunx', 'agentuity', 'deploy', '--trigger', 'cli', '--event', 'manual'];
    logger.debug('[remote-import] Running deploy: %s', args.join(' '));
    const proc = Bun.spawn(args, {
        cwd: dest,
        stdout: 'inherit',
        stderr: 'inherit',
        env: {
            ...process.env,
        },
    });
    const exitCode = await proc.exited;
    if (exitCode !== 0) {
        throw new RemoteImportDeployError({
            message: `Deploy failed with exit code ${exitCode}`,
        });
    }
}
/**
 * Run the remote import flow: download from GitHub, set up project, optionally push and deploy.
 */
export async function runRemoteImport(options) {
    const { url, deploy, projectId, repo, name, env, org, region: optRegion, apiClient, auth, config, logger, } = options;
    // Safety check: refuse to run inside an existing git repo
    try {
        const result = Bun.spawnSync(['git', 'rev-parse', '--is-inside-work-tree'], {
            cwd: process.cwd(),
            stdout: 'pipe',
            stderr: 'pipe',
        });
        if (result.exitCode === 0 && result.stdout.toString().trim() === 'true') {
            tui.fatal('Cannot run remote import inside an existing git repository. Please run from an empty directory.');
        }
    }
    catch {
        // git not found or command failed — not inside a repo, which is fine
    }
    // 1. Parse GitHub URL (async — may query GitHub API for default branch)
    const parsed = await parseGitHubUrl(url, apiClient);
    logger.debug('[remote-import] Parsed URL: owner=%s repo=%s branch=%s dir=%s', parsed.owner, parsed.repo, parsed.branch, parsed.directory ?? '(root)');
    // ── Preflight checks (all before any mutations) ──
    // Check: target directory doesn't already exist
    const projectDirName = name ?? parsed.repo;
    const dest = join(process.cwd(), projectDirName);
    if (existsSync(dest)) {
        tui.fatal(`Directory "${projectDirName}" already exists. Choose a different name with --name.`);
    }
    // Check: target GitHub repo doesn't already exist
    if (repo) {
        const { owner: repoOwner, name: repoName } = parseRepoTarget(repo);
        const checkResult = await tui.spinner({
            message: `Checking repository ${repoOwner}/${repoName}...`,
            clearOnSuccess: true,
            callback: () => checkGithubRepo(apiClient, { owner: repoOwner, name: repoName }),
        });
        if (checkResult.exists) {
            tui.fatal(`Repository ${repoOwner}/${repoName} already exists. Use a different name or delete the existing repo first.`);
        }
    }
    // ── All checks passed — start doing work ──
    // 2. Download and extract template source
    let tempDir;
    let sourceDir;
    try {
        const result = await downloadAndExtract(parsed, apiClient, logger);
        tempDir = result.tempDir;
        sourceDir = result.extractDir;
        // If a specific directory was specified in the URL, navigate into it
        if (parsed.directory) {
            const subDir = join(sourceDir, parsed.directory);
            if (!existsSync(subDir)) {
                throw new RemoteImportDirectoryNotFoundError({
                    message: `Directory "${parsed.directory}" not found in the repository.`,
                });
            }
            sourceDir = subDir;
        }
        // 3. Find and parse agentuity.yaml (informational, for future use)
        const yamlConfig = await findAgentuityYaml(sourceDir, logger);
        if (yamlConfig) {
            tui.info('Found agentuity.yaml in template.');
        }
        // 4. Project setup
        let projectInfo;
        if (projectId) {
            // --project-id was provided: skip creation, just write config
            const sdkKey = process.env.AGENTUITY_SDK_KEY;
            if (!sdkKey) {
                throw new RemoteImportConfigError({
                    message: 'AGENTUITY_SDK_KEY environment variable is required when using --project-id',
                });
            }
            const orgId = org ?? config.preferences?.orgId;
            if (!orgId) {
                throw new RemoteImportConfigError({
                    message: 'Organization ID not found. Use --org flag, set orgId in config preferences, or use interactive mode.',
                });
            }
            const region = process.env.AGENTUITY_REGION ?? config.preferences?.region ?? 'usc';
            projectInfo = { id: projectId, sdkKey, orgId, region };
            tui.info(`Using pre-created project: ${projectId}`);
        }
        else if (name) {
            // --name provided: create non-interactively (headless-friendly)
            projectInfo = await createProjectNonInteractive(apiClient, config, logger, name, optRegion, org);
        }
        else if (isTTY()) {
            // Interactive mode: prompt for org/region/name
            projectInfo = await createProjectInteractive(apiClient, config, logger, parsed.repo);
        }
        else {
            // Non-interactive without --name: use repo name
            projectInfo = await createProjectNonInteractive(apiClient, config, logger, parsed.repo, optRegion, org);
        }
        // Parse .env.example to detect required env vars and resources
        // Platform-managed vars that should not appear in requirements
        const platformManagedVars = new Set([
            'AGENTUITY_SDK_KEY',
            'AGENTUITY_URL',
            'AGENTUITY_TRANSPORT_URL',
            'AGENTUITY_BEARER_TOKEN',
            'NODE_ENV',
        ]);
        let template;
        const envExamplePath = join(sourceDir, '.env.example');
        if (await Bun.file(envExamplePath).exists()) {
            try {
                const envContent = await Bun.file(envExamplePath).text();
                const envFields = parseEnvExample(envContent).filter((f) => !platformManagedVars.has(f.key));
                const resources = envFields
                    .filter((f) => f.resource)
                    .map((f) => ({
                    type: f.resource,
                    envVar: f.key,
                    description: f.comment,
                }));
                const envVars = envFields
                    .filter((f) => !f.resource)
                    .map((f) => ({
                    key: f.key,
                    required: f.required ?? false,
                    description: f.comment,
                }));
                // Merge curated metadata from the source template's existing agentuity.json
                const existingConfigPath = join(sourceDir, 'agentuity.json');
                if (await Bun.file(existingConfigPath).exists()) {
                    try {
                        const existingConfig = JSON.parse(await Bun.file(existingConfigPath).text());
                        const existingResources = existingConfig?.template?.requirements?.resources;
                        if (Array.isArray(existingResources)) {
                            // Merge extra fields (like defaultName) from curated config
                            for (const resource of resources) {
                                const curated = existingResources.find((r) => r.envVar === resource.envVar);
                                if (curated?.defaultName) {
                                    resource.defaultName = curated.defaultName;
                                }
                                if (curated?.queueType) {
                                    resource.queueType = curated.queueType;
                                }
                            }
                            // Preserve curated resources NOT detected by parser
                            for (const curated of existingResources) {
                                if (!resources.some((r) => r.envVar === curated.envVar)) {
                                    resources.push(curated);
                                }
                            }
                        }
                        // Merge curated env vars not detected by parser
                        const existingEnv = existingConfig?.template?.requirements?.env;
                        if (Array.isArray(existingEnv)) {
                            for (const curated of existingEnv) {
                                if (!envVars.some((e) => e.key === curated.key)) {
                                    envVars.push(curated);
                                }
                            }
                        }
                    }
                    catch {
                        // Ignore parse errors — source config may be malformed
                    }
                }
                if (resources.length > 0 || envVars.length > 0) {
                    template = {
                        source: `github.com/${parsed.owner}/${parsed.repo}`,
                        requirements: {
                            resources: resources.length > 0 ? resources : undefined,
                            env: envVars.length > 0 ? envVars : undefined,
                        },
                    };
                    for (const r of resources) {
                        tui.info(`Requires ${r.type}: ${r.envVar}${r.description ? ` (${r.description})` : ''}`);
                    }
                    const requiredEnv = envVars.filter((f) => f.required);
                    for (const f of requiredEnv) {
                        tui.info(`Required env var: ${f.key}${f.description ? ` (${f.description})` : ''}`);
                    }
                }
            }
            catch (err) {
                logger.debug('[remote-import] Could not parse .env.example: %o', err);
            }
        }
        // If no .env.example but we know the source, still track it
        if (!template && parsed.owner && parsed.repo) {
            template = { source: `github.com/${parsed.owner}/${parsed.repo}` };
        }
        // ─── Resource Provisioning ───
        // Parse --env flags into a map
        const envOverrides = new Map();
        for (const e of env ?? []) {
            const colonIdx = e.indexOf(':');
            if (colonIdx > 0) {
                envOverrides.set(e.slice(0, colonIdx), e.slice(colonIdx + 1));
            }
        }
        const resourceEnvVars = {};
        const interactive = isTTY();
        const templateResources = template?.requirements?.resources ?? [];
        const templateEnvVars = template?.requirements?.env ?? [];
        // Check if we can provision (need auth + org + region)
        const orgId = projectInfo.orgId;
        const region = projectInfo.region;
        const canProvision = !!orgId && !!region;
        if (canProvision && (templateResources.length > 0 || templateEnvVars.length > 0)) {
            // ── Preflight validation: check all required items before creating anything ──
            if (!interactive) {
                const missing = [];
                for (const r of templateResources) {
                    if (!envOverrides.has(r.envVar)) {
                        missing.push(`--env ${r.envVar}:<${r.type}-name>`);
                    }
                }
                for (const e of templateEnvVars) {
                    if (e.required && !envOverrides.has(e.key)) {
                        missing.push(`--env ${e.key}:<value>`);
                    }
                }
                if (missing.length > 0) {
                    for (const m of missing) {
                        tui.error(`Missing: ${m}`);
                    }
                    tui.fatal('Provide all required --env flags for non-interactive mode.');
                }
                // Validate database names upfront
                for (const r of templateResources.filter((res) => res.type === 'database')) {
                    const name = envOverrides.get(r.envVar);
                    const validation = validateDatabaseName(name);
                    if (!validation.valid) {
                        tui.fatal(`Invalid database name "${name}" for ${r.envVar}: ${validation.error}`);
                    }
                }
            }
            const catalystClient = getCatalystAPIClient(logger, auth, region, undefined, config);
            // ── Database Resources ──
            for (const r of templateResources.filter((resource) => resource.type === 'database')) {
                const overrideName = envOverrides.get(r.envVar);
                if (overrideName) {
                    // Non-interactive path: create DB with the given name
                    try {
                        const validation = validateDatabaseName(overrideName);
                        if (!validation.valid) {
                            throw new RemoteImportConfigError({
                                message: `Invalid database name "${overrideName}": ${validation.error}`,
                            });
                        }
                        const created = await tui.spinner({
                            message: `Creating database "${overrideName}"`,
                            clearOnSuccess: true,
                            callback: () => createResources(catalystClient, orgId, region, [
                                { type: 'db', name: overrideName, description: r.description },
                            ]),
                        });
                        if (created[0]?.env) {
                            // Map using the template-defined envVar name
                            const connStr = created[0].env.DATABASE_URL ?? Object.values(created[0].env)[0];
                            if (connStr)
                                resourceEnvVars[r.envVar] = connStr;
                        }
                        tui.success(`Created database: ${overrideName}`);
                    }
                    catch (err) {
                        const msg = err instanceof Error ? err.message : String(err);
                        if (!interactive) {
                            throw new RemoteImportConfigError({
                                message: `Failed to create database "${overrideName}": ${msg}`,
                            });
                        }
                        tui.error(`Failed to create database "${overrideName}": ${msg}`);
                        // Fall through to interactive prompt below
                    }
                }
                // Interactive fallback (no --env or --env failed)
                if (!resourceEnvVars[r.envVar] && interactive) {
                    const prompt = createPrompt();
                    let existingDbs;
                    try {
                        existingDbs = await tui.spinner({
                            message: 'Fetching existing databases',
                            clearOnSuccess: true,
                            callback: () => listResources(catalystClient, orgId, region),
                        });
                    }
                    catch {
                        // Ignore — just won't show existing options
                    }
                    let dbCreated = false;
                    while (!dbCreated) {
                        const action = await prompt.select({
                            message: `${r.description || r.envVar} requires a database`,
                            options: [
                                { value: 'skip', label: 'Skip — set up later' },
                                { value: 'create', label: 'Create a new database' },
                                ...(existingDbs?.db ?? []).map((db) => ({
                                    value: `existing:${db.name}`,
                                    label: `Use existing: ${tui.tuiColors.primary(db.name)}`,
                                })),
                            ],
                        });
                        if (action === 'skip')
                            break;
                        if (action === 'create') {
                            const dbName = await prompt.text({
                                message: 'Database name',
                                hint: 'Lowercase letters, digits, underscores only',
                                initial: r.defaultName,
                                validate: (value) => {
                                    const trimmed = value.trim();
                                    if (trimmed === '')
                                        return 'Name is required';
                                    const result = validateDatabaseName(trimmed);
                                    return result.valid ? true : result.error;
                                },
                            });
                            try {
                                const created = await tui.spinner({
                                    message: `Creating database "${dbName}"`,
                                    clearOnSuccess: true,
                                    callback: () => createResources(catalystClient, orgId, region, [
                                        { type: 'db', name: dbName.trim(), description: r.description },
                                    ]),
                                });
                                if (created[0]?.env) {
                                    const connStr = created[0].env.DATABASE_URL ?? Object.values(created[0].env)[0];
                                    if (connStr)
                                        resourceEnvVars[r.envVar] = connStr;
                                }
                                tui.success(`Created database: ${dbName}`);
                                dbCreated = true;
                            }
                            catch (err) {
                                tui.error(`Failed to create database: ${err instanceof Error ? err.message : String(err)}`);
                                // Loop back to prompt
                            }
                        }
                        else if (action.startsWith('existing:')) {
                            const selectedName = action.slice('existing:'.length);
                            const selectedDb = existingDbs?.db.find((d) => d.name === selectedName);
                            if (selectedDb?.env) {
                                const connStr = selectedDb.env.DATABASE_URL ?? Object.values(selectedDb.env)[0];
                                if (connStr)
                                    resourceEnvVars[r.envVar] = connStr;
                            }
                            dbCreated = true;
                        }
                    }
                }
                if (!resourceEnvVars[r.envVar] && !interactive) {
                    throw new RemoteImportConfigError({
                        message: `Missing required database for ${r.envVar}. Pass --env ${r.envVar}:<db-name> to provision.`,
                    });
                }
            }
            // ── Queue Resources ──
            const queueClient = await getGlobalCatalystAPIClient(logger, auth, config?.name, undefined, config);
            const queueOrgOpts = orgId ? { orgId } : undefined;
            for (const r of templateResources.filter((resource) => resource.type === 'queue')) {
                if (!r.queueType) {
                    logger.debug('[remote-import] Queue resource %s missing queueType, skipping', r.envVar);
                    continue;
                }
                const overrideName = envOverrides.get(r.envVar);
                if (overrideName) {
                    // Non-interactive path: create queue with given name
                    try {
                        const queue = await tui.spinner({
                            message: `Creating ${r.queueType} queue "${overrideName}"`,
                            clearOnSuccess: true,
                            callback: () => createQueue(queueClient, {
                                name: overrideName,
                                queue_type: r.queueType,
                                description: r.description,
                            }, queueOrgOpts),
                        });
                        resourceEnvVars[r.envVar] = queue.name;
                        tui.success(`Created queue: ${queue.name}`);
                    }
                    catch (err) {
                        const msg = err instanceof Error ? err.message : String(err);
                        if (!interactive) {
                            throw new RemoteImportConfigError({
                                message: `Failed to create queue "${overrideName}": ${msg}`,
                            });
                        }
                        tui.error(`Failed to create queue "${overrideName}": ${msg}`);
                    }
                }
                // Interactive fallback
                if (!resourceEnvVars[r.envVar] && interactive) {
                    const prompt = createPrompt();
                    let existingQueues;
                    try {
                        existingQueues = await tui.spinner({
                            message: 'Fetching existing queues',
                            clearOnSuccess: true,
                            callback: () => listQueues(queueClient, {}, queueOrgOpts),
                        });
                    }
                    catch {
                        // Ignore
                    }
                    let queueCreated = false;
                    while (!queueCreated) {
                        const action = await prompt.select({
                            message: `${r.description || r.envVar} requires a ${r.queueType} queue`,
                            options: [
                                { value: 'skip', label: 'Skip — set up later' },
                                { value: 'create', label: `Create a new ${r.queueType} queue` },
                                ...(existingQueues?.queues ?? []).map((q) => ({
                                    value: `existing:${q.name}`,
                                    label: `Use existing: ${tui.tuiColors.primary(q.name)}`,
                                })),
                            ],
                        });
                        if (action === 'skip')
                            break;
                        if (action === 'create') {
                            const queueName = await prompt.text({
                                message: 'Queue name',
                                hint: 'Optional — auto-generated if empty',
                                initial: r.defaultName,
                            });
                            try {
                                const queue = await tui.spinner({
                                    message: `Creating ${r.queueType} queue "${queueName || '(auto)'}"`,
                                    clearOnSuccess: true,
                                    callback: () => createQueue(queueClient, {
                                        name: queueName.trim() || undefined,
                                        queue_type: r.queueType,
                                        description: r.description,
                                    }, queueOrgOpts),
                                });
                                resourceEnvVars[r.envVar] = queue.name;
                                tui.success(`Created queue: ${queue.name}`);
                                queueCreated = true;
                            }
                            catch (err) {
                                tui.error(`Failed to create queue: ${err instanceof Error ? err.message : String(err)}`);
                            }
                        }
                        else if (action.startsWith('existing:')) {
                            const selectedName = action.slice('existing:'.length);
                            resourceEnvVars[r.envVar] = selectedName;
                            queueCreated = true;
                        }
                    }
                }
                if (!resourceEnvVars[r.envVar] && !interactive) {
                    throw new RemoteImportConfigError({
                        message: `Missing required queue for ${r.envVar}. Pass --env ${r.envVar}:<queue-name> to provision.`,
                    });
                }
            }
            // ── Plain Env Vars ──
            for (const e of templateEnvVars) {
                if (envOverrides.has(e.key)) {
                    resourceEnvVars[e.key] = envOverrides.get(e.key);
                }
                else if (e.required) {
                    if (interactive) {
                        const prompt = createPrompt();
                        const val = await prompt.text({
                            message: `Enter value for ${e.key}${e.description ? ` (${e.description})` : ''}`,
                        });
                        if (val.trim()) {
                            resourceEnvVars[e.key] = val.trim();
                        }
                    }
                    else {
                        throw new RemoteImportConfigError({
                            message: `Missing required env var ${e.key}. Pass --env ${e.key}:<value> to set it.`,
                        });
                    }
                }
            }
            // Write all collected env vars to .env
            if (Object.keys(resourceEnvVars).length > 0) {
                await addResourceEnvVars(sourceDir, resourceEnvVars);
                tui.success(`Configured ${Object.keys(resourceEnvVars).length} environment variable(s)`);
            }
        }
        // Write agentuity.json and .env to sourceDir so git commit includes them
        await createProjectConfig(sourceDir, {
            projectId: projectInfo.id,
            orgId: projectInfo.orgId,
            sdkKey: projectInfo.sdkKey,
            region: projectInfo.region,
            template,
        });
        tui.success('Created agentuity.json');
        // Ensure .env is gitignored before any git operations (prevents secret leak)
        const gitignorePath = join(sourceDir, '.gitignore');
        const gitignoreFile = Bun.file(gitignorePath);
        if (await gitignoreFile.exists()) {
            const gitignoreContent = await gitignoreFile.text();
            const lines = gitignoreContent.split('\n').map((l) => l.trim());
            if (!lines.includes('.env')) {
                await Bun.write(gitignorePath, `${gitignoreContent.trimEnd()}\n.env\n`);
            }
        }
        else {
            await Bun.write(gitignorePath, '.env\n.env.*\n');
        }
        // Update package.json name to match the project name
        const pkgJsonPath = join(sourceDir, 'package.json');
        if (await Bun.file(pkgJsonPath).exists()) {
            try {
                const pkgRaw = await Bun.file(pkgJsonPath).text();
                const pkg = JSON.parse(pkgRaw);
                pkg.name = projectDirName;
                await Bun.write(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n');
                logger.debug('[remote-import] Updated package.json name to %s', projectDirName);
            }
            catch (err) {
                logger.debug('[remote-import] Could not update package.json name: %o', err);
            }
        }
        // Fetch GitHub App bot identity for commit authorship
        let botAuthor;
        try {
            botAuthor = await getGithubBotIdentity(apiClient);
        }
        catch {
            logger.debug('[remote-import] Could not fetch bot identity, using fallback');
        }
        // 5. Git init + push (if --repo flag provided) — in sourceDir, not CWD
        if (repo) {
            // Create the repo (we already verified it doesn't exist in preflight)
            const repoUrl = await createGithubRepoForImport(apiClient, repo, logger);
            // Initialize git repo in sourceDir (handles init + first commit)
            await initGitRepo(sourceDir, {
                projectName: projectDirName,
                source: `github.com/${parsed.owner}/${parsed.repo}`,
                author: botAuthor,
            });
            // Push to remote from sourceDir
            await pushToRepo(sourceDir, repoUrl, apiClient, logger);
            tui.success(`GitHub repo: ${repoUrl}`);
            // Link the repo to the Agentuity project (auto-deploy + preview disabled until first deploy completes)
            try {
                const { owner: linkOwner, name: linkName } = parseRepoTarget(repo);
                const pushedBranch = (await getDefaultBranch()) || 'main';
                await linkProjectToRepo(apiClient, {
                    projectId: projectInfo.id,
                    repoFullName: `${linkOwner}/${linkName}`,
                    branch: pushedBranch,
                    autoDeploy: false,
                    previewDeploy: false,
                    directory: parsed.directory,
                });
                tui.success('Linked repo to project');
            }
            catch (err) {
                logger.debug('[remote-import] Failed to link repo to project: %o', err);
                tui.warning('Could not link repo to project — you can link manually with `agentuity link`');
            }
        }
        // 6. Copy extracted content into project folder (already validated in preflight)
        await tui.spinner({
            message: 'Copying project files...',
            clearOnSuccess: true,
            callback: async () => {
                mkdirSync(dest, { recursive: true });
                const entries = readdirSync(sourceDir);
                for (const entry of entries) {
                    cpSync(join(sourceDir, entry), join(dest, entry), {
                        recursive: true,
                    });
                }
            },
        });
        // Reset git remote to clean URL (pushToRepo may have embedded a token)
        if (repo) {
            const { owner, name: repoName } = parseRepoTarget(repo);
            const cleanUrl = `https://github.com/${owner}/${repoName}.git`;
            Bun.spawnSync(['git', 'remote', 'set-url', 'origin', cleanUrl], {
                cwd: dest,
                stdout: 'pipe',
                stderr: 'pipe',
            });
        }
        tui.success(`Project created in ./${projectDirName}`);
        // 7. Deploy (if --deploy flag)
        if (deploy) {
            await runDeploy(dest, logger);
        }
        tui.success('Remote import completed successfully!');
    }
    finally {
        // Clean up temp directory
        if (tempDir) {
            try {
                rmSync(tempDir, { recursive: true, force: true });
                logger.debug('[remote-import] Cleaned up temp dir: %s', tempDir);
            }
            catch {
                // Ignore cleanup errors
            }
        }
    }
}
//# sourceMappingURL=remote-import.js.map