/**
 * studiograph join command
 *
 * Joins an existing Studiograph workspace by connecting to a running
 * Studiograph server, authenticating with email/password, and cloning
 * all accessible folders via the embedded git server.
 */
import { Command } from 'commander';
import { intro, confirm, outro, spinner, log, text, password as promptPassword, isCancel } from '@clack/prompts';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { join, resolve } from 'path';
import { gitClone } from '../../utils/git.js';
import { runPreflight } from '../../utils/preflight.js';
import { reindexWorkspace } from '../../services/vector-service.js';
import { Workspace } from '../../core/workspace.js';
import { ConnectorManager } from '../../mcp/connector-manager.js';
import { promptLLMSetupIfNeeded } from '../setup-wizard.js';
/**
 * Fetch JSON from the server with Basic Auth.
 */
async function serverFetch(serverUrl, path, email, password) {
    const headers = {
        'Authorization': 'Basic ' + Buffer.from(`${email}:${password}`).toString('base64'),
        'Accept': 'application/json',
    };
    const res = await fetch(`${serverUrl}${path}`, { headers });
    if (!res.ok) {
        if (res.status === 401)
            throw new Error('Invalid credentials');
        throw new Error(`Server returned ${res.status}: ${res.statusText}`);
    }
    return res.json();
}
export const joinCommand = new Command('join')
    .description('Join an existing Studiograph workspace')
    .argument('<server-url>', 'URL of the Studiograph server (e.g. https://studio.example.com)')
    .argument('[directory]', 'Directory to create workspace in (default: derived from workspace name, use "." for current directory)')
    .action(async (serverUrl, directory) => {
    intro('Join Studiograph Workspace');
    const ready = runPreflight();
    if (!ready) {
        process.exit(1);
        return;
    }
    // Normalize URL — strip trailing slash
    serverUrl = serverUrl.replace(/\/+$/, '');
    try {
        // Step 1: Prompt for credentials
        const email = await text({ message: 'Email:', validate: (v) => v.includes('@') ? undefined : 'Enter a valid email' });
        if (isCancel(email)) {
            outro('Cancelled');
            process.exit(0);
            return;
        }
        const pass = await promptPassword({ message: 'Password:' });
        if (isCancel(pass)) {
            outro('Cancelled');
            process.exit(0);
            return;
        }
        // Step 2: Connect to server and fetch workspace info
        const connectSpinner = spinner();
        connectSpinner.start('Connecting to server...');
        let workspaceName;
        let repos;
        try {
            const wsInfo = await serverFetch(serverUrl, '/api/workspace', email, pass);
            workspaceName = wsInfo.name;
            repos = await serverFetch(serverUrl, '/api/repos', email, pass);
            connectSpinner.stop(`Connected to ${workspaceName}`);
        }
        catch (err) {
            connectSpinner.stop('Connection failed');
            outro(`Could not connect: ${err instanceof Error ? err.message : 'Unknown error'}`);
            process.exit(1);
            return;
        }
        log.info(`${repos.length} folder${repos.length === 1 ? '' : 's'} accessible`);
        if (repos.length === 0) {
            outro('No accessible folders. Ask a workspace admin to grant you access.');
            process.exit(0);
            return;
        }
        // Step 3: Determine target directory
        let targetDir;
        if (directory === '.') {
            targetDir = process.cwd();
        }
        else if (directory) {
            targetDir = resolve(directory);
        }
        else {
            const slug = workspaceName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
            targetDir = resolve(slug);
        }
        // Check if target directory has .studiograph
        const studiographDir = join(targetDir, '.studiograph');
        if (existsSync(studiographDir)) {
            outro(`A .studiograph directory already exists at ${targetDir}`);
            process.exit(1);
            return;
        }
        // Create target directory if needed
        if (!existsSync(targetDir)) {
            mkdirSync(targetDir, { recursive: true });
            log.info(`Created directory: ${targetDir}`);
        }
        else if (directory !== '.') {
            const { readdirSync } = await import('fs');
            const files = readdirSync(targetDir);
            if (files.length > 0) {
                const proceed = await confirm({
                    message: `Directory ${targetDir} is not empty. Continue anyway?`,
                    initialValue: false,
                });
                if (!proceed || typeof proceed === 'symbol') {
                    outro('Cancelled');
                    process.exit(0);
                    return;
                }
            }
        }
        // Confirm before cloning
        const repoList = repos.map(r => `  \u2022 ${r.name}`).join('\n');
        const shouldProceed = await confirm({
            message: `Clone these folders into ${targetDir}?\n${repoList}`,
        });
        if (!shouldProceed || typeof shouldProceed === 'symbol') {
            outro('Cancelled');
            process.exit(0);
            return;
        }
        // Step 4: Create local .studiograph/workspace.json.
        //
        // Phase 9b: route through `Workspace.writeConfig()` so the schema
        // validator runs uniformly across every workspace.json save site.
        mkdirSync(studiographDir, { recursive: true });
        const workspaceConfig = {
            version: '1.0.0',
            team_name: workspaceName,
            created_at: new Date().toISOString(),
            server_url: serverUrl,
            repos: repos.map(r => ({
                name: r.name,
                path: r.name,
                description: r.description,
            })),
        };
        new Workspace(targetDir).writeConfig(workspaceConfig);
        // Create .gitignore for .studiograph
        writeFileSync(join(studiographDir, '.gitignore'), ['auth.db', 'auth-seed.json', 'server-auth.json', 'populate-cache/', 'memory/', '*.log'].join('\n') + '\n', 'utf-8');
        // Step 5: Clone accessible repos (one spinner reused across iterations;
        // @clack's spinner() registers process listeners per call and never removes them).
        let cloned = 0;
        let failed = 0;
        const repoSpinner = spinner();
        for (const repo of repos) {
            const repoPath = join(targetDir, repo.name);
            if (existsSync(repoPath)) {
                log.warn(`Skipping ${repo.name}: directory already exists`);
                continue;
            }
            repoSpinner.start(`Cloning ${repo.name}...`);
            try {
                const gitUrl = `${serverUrl}/git/${repo.name}`;
                gitClone(gitUrl, repoPath, { credentials: { username: email, password: pass } });
                repoSpinner.stop(`Cloned ${repo.name}`);
                cloned++;
            }
            catch {
                repoSpinner.stop(`Failed to clone ${repo.name}`);
                log.warn(`Could not clone ${repo.name}: check your access permissions`);
                failed++;
            }
        }
        // Step 5b: Fetch config bundle (sources, connectors, ABOUT.md) — admin only, non-fatal
        try {
            // Login to get session cookie
            const loginRes = await fetch(`${serverUrl}/api/auth/login`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ email, password: pass }),
            });
            if (loginRes.ok) {
                const cookies = loginRes.headers.getSetCookie?.() ?? [];
                const tokenCookie = cookies.find((c) => c.startsWith('__sg_token='));
                if (tokenCookie) {
                    const cookieHeader = tokenCookie.split(';')[0];
                    // Save JWT for future pull/push (per-user access control in git HTTP auth)
                    const jwtToken = cookieHeader.replace('__sg_token=', '');
                    writeFileSync(join(studiographDir, 'server-auth.json'), JSON.stringify({ serverUrl, email: String(email), jwtToken }, null, 2), { encoding: 'utf-8', mode: 0o600 });
                    const bundleRes = await fetch(`${serverUrl}/api/config/bundle`, {
                        headers: { 'Cookie': cookieHeader },
                    });
                    if (bundleRes.ok) {
                        const bundle = await bundleRes.json();
                        // Write sources
                        if (bundle.sources && Object.keys(bundle.sources).length > 0) {
                            const sourcesDir = join(studiographDir, 'sources');
                            mkdirSync(sourcesDir, { recursive: true });
                            for (const [name, def] of Object.entries(bundle.sources)) {
                                writeFileSync(join(sourcesDir, `${name}.json`), JSON.stringify(def, null, 2), 'utf-8');
                            }
                            log.info(`Synced ${Object.keys(bundle.sources).length} source definition(s)`);
                        }
                        // Write connectors (sanitized).
                        //
                        // Phase 9b: route through ConnectorManager.saveWorkspaceConfig
                        // so the schema validator runs uniformly.
                        if (bundle.connectors?.length > 0) {
                            for (const conn of bundle.connectors) {
                                ConnectorManager.saveWorkspaceConfig(conn, targetDir);
                            }
                            log.info(`Synced ${bundle.connectors.length} connector config(s)`);
                        }
                        // Write ABOUT.md
                        if (bundle.about) {
                            writeFileSync(join(studiographDir, 'ABOUT.md'), bundle.about, 'utf-8');
                        }
                    }
                }
            }
        }
        catch (err) {
            // Phase 9c: schema violations surface — they mean the server
            // shipped a literal secret. Everything else stays non-fatal
            // (config bundle is optional during join).
            if (err?.name === 'SchemaViolationError' || err?.code === 'schema_violation') {
                log.warn(`Connector config validation failed (skipped): ${err.message}`);
            }
        }
        // Step 6: Index entities for search
        if (cloned > 0) {
            const indexSpinner = spinner();
            indexSpinner.start('Building search index...');
            try {
                const indexedRepos = repos.filter(r => existsSync(join(targetDir, r.name)));
                const total = await reindexWorkspace(targetDir, indexedRepos);
                indexSpinner.stop(`Search index ready (${total} entities)`);
            }
            catch {
                indexSpinner.stop('Search index skipped (non-fatal)');
            }
        }
        // Step 7: Summary
        const parts = [];
        if (cloned > 0)
            parts.push(`${cloned} cloned`);
        if (failed > 0)
            parts.push(`${failed} failed`);
        const summary = parts.join(', ');
        // Step 8: AI model setup
        await promptLLMSetupIfNeeded();
        const nextStep = directory === '.' ? '' : `\n\nNext: cd ${targetDir} && studiograph start`;
        if (failed > 0) {
            outro(`Joined ${workspaceName} with issues (${summary}). Ask a workspace admin to verify your access.${nextStep}`);
        }
        else {
            outro(`Joined ${workspaceName} workspace (${summary})${nextStep}`);
        }
    }
    catch (error) {
        outro(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
        process.exit(1);
    }
});
//# sourceMappingURL=join.js.map