"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerCreateZeroToOneTool = exports.normalizeWorkspaceRoot = exports.slugifyPrompt = exports.createZeroToOneInput = void 0;
const crypto_1 = require("crypto");
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const v3_1 = require("zod/v3");
const viteReactTs_1 = require("../../services/templates/viteReactTs");
const contracts_1 = require("./contracts");
const errors_1 = require("./errors");
const SessionStore_1 = require("./SessionStore");
const sourceContext_1 = require("./sourceContext");
const DEFAULT_VARIANT_COUNT = 3;
const MIN_VARIANT_COUNT = 2;
const MAX_VARIANT_COUNT = 8;
const SLUG_SUFFIX_BYTES = 2;
exports.createZeroToOneInput = {
    prompt: v3_1.z.string().min(1),
    count: v3_1.z.number().int().min(MIN_VARIANT_COUNT).max(MAX_VARIANT_COUNT).optional(),
    destinationParent: v3_1.z.string().optional(),
    framework: v3_1.z.enum(['vite']).optional(),
    sourceContext: contracts_1.sourceContextInputSchema.optional(),
    sessionId: v3_1.z.string().optional(),
};
const createZeroToOneToolInput = exports.createZeroToOneInput;
const proposeVariantsInputSchema = v3_1.z.object(contracts_1.proposeVariantsInput);
/**
 * Slugify a prompt into a directory-safe kebab-case name with a short random
 * suffix so concurrent calls with the same prompt don't collide.
 */
const slugifyPrompt = (prompt) => {
    const base = prompt
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '')
        .slice(0, 40);
    const suffix = (0, crypto_1.randomBytes)(SLUG_SUFFIX_BYTES).toString('hex');
    return base ? `${base}-${suffix}` : `rivet-app-${suffix}`;
};
exports.slugifyPrompt = slugifyPrompt;
const failed = (code, message) => ({
    stage: 'failed',
    errorCode: code,
    message,
});
/**
 * Collapse a candidate workspace root back to the real workspace when it points
 * inside a `.rivet/` tree.
 *
 * The editor bridge can become bound to a previously generated subproject
 * (`<root>/.rivet/<slug>`) — e.g. after the user opens that subproject in the
 * visual editor. Without this guard the next zero-to-one session would resolve
 * its `destinationParent` to that subproject and join `.rivet/<slug>` onto it,
 * producing a nested `<root>/.rivet/<slug>/.rivet/<slug2>`. That nesting breaks
 * preview/history path resolution (everything is rooted at `workspaceRoot`), so
 * generated variants land on disk but render blank on click.
 *
 * Truncating at the segment before the first `.rivet` keeps the single-`.rivet`
 * invariant. Idempotent: a path with no `.rivet` segment is returned unchanged.
 */
const normalizeWorkspaceRoot = (candidate) => {
    const resolved = path_1.default.resolve(candidate);
    const segments = resolved.split(path_1.default.sep);
    const idx = segments.indexOf('.rivet');
    if (idx <= 0)
        return resolved;
    return segments.slice(0, idx).join(path_1.default.sep) || path_1.default.sep;
};
exports.normalizeWorkspaceRoot = normalizeWorkspaceRoot;
/**
 * Returns true when a file-copy root is specific enough to authorize asset
 * reads. Broad roots such as the home directory or filesystem root would let
 * an agent-controlled asset plan copy unrelated personal files.
 */
const isSpecificAssetSourceRoot = (root, homePath) => {
    const resolvedRoot = path_1.default.resolve(root);
    return (resolvedRoot !== path_1.default.resolve(homePath) &&
        resolvedRoot !== path_1.default.parse(resolvedRoot).root);
};
/**
 * Register the `create_zero_to_one_project` convenience MCP tool. Normalizes
 * the variant count, slugifies the prompt to derive a destination directory,
 * validates the framework, and delegates to `proposeVariantsHandler` with
 * `projectContext.kind = 'fresh'`.
 */
const registerCreateZeroToOneTool = (mcp, deps) => {
    const homedir = deps.homedir ?? os_1.default.homedir;
    mcp.tool('create_zero_to_one_project', 'Entry point for source-grounded fresh projects (inspiration URLs, attached images, attached videos, custom design context). Accepts inspiration as URLs via sourceContext.sourceUrls and/or as agent-described sourceContext.sourceArtifacts: kind="visualBrief" for an overall design system, kind="screenshotNotes" for a specific screen, kind="motionBrief" for a structured motion spec derived from a video/gif/screen recording (see the Describe Motion protocol in the rivet skill). When the user pastes an image in chat or references an image file path, describe it into a sourceArtifacts entry rather than fabricating a URL; image-only or video-only input (no sourceUrls) is supported. Slugifies the prompt into a destination directory under the user home, runs the source-research flow, and outputs static_preview HTML/CSS deliverables. Once briefs are reported the directions start generating immediately — there is no in-chat approval gate. Prefer start_variants(mode="zero_to_one") for prompt-only fresh projects without inspiration sources. Full protocol in the rivet skill.', createZeroToOneToolInput, async (rawArgs) => {
        const args = rawArgs;
        try {
            const framework = args.framework ?? 'vite';
            if (framework !== 'vite') {
                return jsonResponse(failed('UNSUPPORTED_FRAMEWORK', `create_zero_to_one_project only supports framework='vite' in v1; got '${framework}'.`));
            }
            const count = clampCount(args.count);
            const inputSourceUrls = args.sourceContext?.sourceUrls ?? [];
            const sourceUrls = (0, sourceContext_1.resolveSourceUrls)(args.prompt, args.sourceContext);
            // Compare raw inputs in their normalized form so URLs that only
            // differ by hash fragments or trailing punctuation aren't reported
            // as dropped when they were in fact included.
            const resolvedSet = new Set(sourceUrls);
            const droppedUrls = inputSourceUrls.filter((u) => {
                const normalized = (0, sourceContext_1.normalizeUrl)(u);
                return normalized === null || !resolvedSet.has(normalized);
            });
            const warnings = droppedUrls.map((url) => `Skipped source URL '${url}' — failed validation (private network, unsupported scheme, or unreachable).`);
            const sourceArtifacts = (0, sourceContext_1.normalizeSourceArtifacts)(args.sourceContext?.sourceArtifacts);
            const projectPath = deps.getProjectPath?.() ?? undefined;
            const cwd = deps.cwd ?? (() => process.cwd());
            // Default chain: explicit arg → currently-open editor project →
            // MCP server cwd. cwd (not homedir) so generated projects land
            // near where the user is actually working — their shell's PWD
            // when claude-code spawned the MCP server — rather than dumped
            // at the home root.
            const destinationParent = args.destinationParent ?? projectPath ?? cwd();
            if (!path_1.default.isAbsolute(destinationParent)) {
                return jsonResponse(failed('SCHEMA_VALIDATION_FAILED', `destinationParent must be absolute; got '${destinationParent}'.`));
            }
            const slug = (0, exports.slugifyPrompt)(args.prompt);
            // The generated subproject lives under `<workspaceRoot>/.rivet/<slug>/`
            // so the user's working dir stays clean — every Rivet artifact
            // (subprojects, variant history, create-project manifests, snapshots)
            // sits inside the single `.rivet/` directory.
            //
            // Normalize first: if the resolved parent already sits inside a
            // `.rivet/` tree (e.g. the editor was bound to a prior subproject),
            // collapse back to the real workspace root so we never nest one
            // `.rivet/` inside another — which would break preview resolution.
            const workspaceRoot = (0, exports.normalizeWorkspaceRoot)(destinationParent);
            const destinationPath = path_1.default.join(workspaceRoot, '.rivet', slug);
            if (!(0, viteReactTs_1.isDestinationEmpty)(destinationPath)) {
                return jsonResponse(failed('DESTINATION_NOT_EMPTY', `Destination '${destinationPath}' is not empty.`));
            }
            const assetSourceRoot = projectPath && isSpecificAssetSourceRoot(projectPath, homedir())
                ? path_1.default.resolve(projectPath)
                : undefined;
            const projectContext = {
                kind: 'fresh',
                workspacePath: destinationPath,
                workspaceRoot,
                framework: 'vite',
                ...(assetSourceRoot ? { assetSourceRoot } : {}),
                ...(sourceUrls.length > 0 ||
                    sourceArtifacts.length > 0 ||
                    args.sourceContext?.preserveBrand !== undefined
                    ? {
                        sourceContext: {
                            ...(sourceUrls.length > 0 ? { sourceUrls } : {}),
                            ...(sourceArtifacts.length > 0 ? { sourceArtifacts } : {}),
                            ...(args.sourceContext?.preserveBrand !== undefined
                                ? { preserveBrand: args.sourceContext.preserveBrand }
                                : {}),
                        },
                    }
                    : {}),
            };
            // Ensure the destination's parent (`<workspaceRoot>/.rivet/`) exists
            // so the materialize step at commit time has a directory to write
            // into. The destination itself is created during materialize.
            fs_1.default.mkdirSync(path_1.default.dirname(destinationPath), { recursive: true });
            const proposeInput = {
                sessionId: args.sessionId,
                prompt: args.prompt,
                count,
                projectContext,
            };
            // Validate via the shared schema before delegating, so the proxy and
            // direct callers see the same rejection shape.
            const parsed = proposeVariantsInputSchema.safeParse(proposeInput);
            if (!parsed.success) {
                return jsonResponse(failed('SCHEMA_VALIDATION_FAILED', parsed.error.message));
            }
            const visualEditor = deps.ensureVisualEditorOpen
                ? await deps.ensureVisualEditorOpen({
                    projectPath: destinationPath,
                    workspaceRoot,
                    framework: 'vite',
                })
                : undefined;
            const result = deps.orchestrator.propose(parsed.data);
            if (sourceUrls.length > 0 || sourceArtifacts.length > 0) {
                deps.telemetry?.trackAgentVariantsSourceContextQuality?.({
                    sessionId: result.sessionId,
                    sourceUrlCount: sourceUrls.length,
                    artifactCount: sourceArtifacts.length,
                    hasScreenshotReferences: sourceArtifacts.some((artifact) => artifact.kind === 'screenshotNotes'),
                    preserveBrand: Boolean(args.sourceContext?.preserveBrand),
                });
            }
            if (result.stage === 'awaiting_source_plan') {
                return jsonResponse({
                    sessionId: result.sessionId,
                    stage: 'awaiting_source_plan',
                    sourcePlanWorkItem: {
                        id: result.sourcePlanWorkItem.id,
                        kind: 'source_plan',
                        attempt: result.sourcePlanWorkItem.attempt,
                        input: result.sourcePlanWorkItem.input,
                        output_schema: SessionStore_1.SOURCE_PLAN_OUTPUT_SCHEMA,
                    },
                    nextAction: 'continue_variants',
                    ...(visualEditor ? { visualEditor } : {}),
                    ...(warnings.length > 0 ? { warnings } : {}),
                });
            }
            return jsonResponse({
                sessionId: result.sessionId,
                stage: 'awaiting_briefs',
                briefWorkItem: {
                    id: result.briefWorkItem.id,
                    kind: 'brief',
                    attempt: result.briefWorkItem.attempt,
                    input: result.briefWorkItem.input,
                    output_schema: { briefs: 'Array<{briefId, label, body}>' },
                },
                nextAction: 'report_variant_briefs',
                ...(visualEditor ? { visualEditor } : {}),
                ...(warnings.length > 0 ? { warnings } : {}),
            });
        }
        catch (err) {
            const code = err instanceof errors_1.AgentVariantsError
                ? err.code
                : 'SCHEMA_VALIDATION_FAILED';
            const message = err instanceof Error
                ? err.message
                : 'Unknown error in create_zero_to_one_project';
            return jsonResponse({
                stage: 'failed',
                errorCode: code,
                message,
            });
        }
    });
};
exports.registerCreateZeroToOneTool = registerCreateZeroToOneTool;
const clampCount = (count) => {
    if (count === undefined)
        return DEFAULT_VARIANT_COUNT;
    if (count < MIN_VARIANT_COUNT)
        return MIN_VARIANT_COUNT;
    if (count > MAX_VARIANT_COUNT)
        return MAX_VARIANT_COUNT;
    return count;
};
const jsonResponse = (payload) => ({
    content: [
        {
            type: 'text',
            text: JSON.stringify(payload),
        },
    ],
});
//# sourceMappingURL=createZeroToOneTool.js.map