/**
 * Promote a conversation-scratch file (image OR doc) into a curated,
 * library-visible asset (composer-vision-attach / E2a "Save to library",
 * generalized to any file in PR3).
 *
 * Uploaded scratch files live in the `chat-scratch/{sessionId}/{fileId}/{filename}`
 * keyspace with NO asset-index row — they're ephemeral conversation context,
 * not catalogued assets (see the E2a section of
 * `~/.claude/plans/composer-vision-attach.md`). This service is the deliberate
 * retention path: it copies the scratch bytes into a FREE-STANDING asset (new
 * immutable assetId + index row + caption trigger), exactly like the global
 * asset-manager upload, so the result shows up in the asset library.
 *
 * Default destination = the reserved workspace-wide "Assets" folder
 * (`workspace-assets`), which is lazily auto-provisioned and undeletable — so
 * unlike a per-user personal folder it is ALWAYS resolvable and writable by any
 * member. The user can then move it into a specific folder via the existing
 * asset-manager move UI. A caller may override with an explicit `targetRepo`
 * (the agent tool exposes this as a `folder` argument); we verify write access
 * to it first.
 *
 * The scratch copy is intentionally LEFT in place — it remains the
 * conversation's vision context and is cleaned up when the chat is deleted.
 */
import { Workspace } from '../core/workspace.js';
import { resolveWorkspaceAssetsRepo } from './workspace-assets-folder.js';
import { deriveAssetDisplayName } from '../core/embeds.js';
import * as access from './access-control.js';
export class PromoteScratchError extends Error {
    code;
    constructor(code, message) {
        super(message);
        this.code = code;
        this.name = 'PromoteScratchError';
    }
}
/**
 * Promote one scratch file (image OR doc) to a free-standing asset. Returns the
 * new asset's coordinates. Throws {@link PromoteScratchError} on the typed
 * failures above.
 */
export async function promoteScratchFileToAssets(workspaceManager, chatSessionService, authService, args) {
    const { sessionId, fileId, user, targetRepo } = args;
    // 1. Authorize: the scratch row must exist AND be owned by the caller. Same
    //    fail-closed posture as the serve route — never reveal another user's row.
    const row = chatSessionService.getScratchFile(sessionId, fileId);
    if (!row || row.userId !== user.id) {
        throw new PromoteScratchError('SCRATCH_NOT_FOUND', 'File not found.');
    }
    // A library-sourced file is ALREADY a curated asset — nothing to promote.
    if (row.source === 'library') {
        throw new PromoteScratchError('NOT_SAVABLE', 'This file is already in your library.');
    }
    // 2. Resolve the destination folder.
    let repo;
    if (targetRepo) {
        if (!access.canWriteRepo(user, targetRepo, workspaceManager, authService)) {
            throw new PromoteScratchError('NO_WRITE_ACCESS', `You can't save to "${targetRepo}".`);
        }
        repo = targetRepo;
    }
    else {
        // Default: the reserved workspace-wide Assets store (lazy create-if-missing,
        // undeletable → always resolvable + writable by any member).
        const workspace = new Workspace(workspaceManager.getWorkspacePath());
        repo = await resolveWorkspaceAssetsRepo(workspace, workspaceManager);
    }
    // 3. Copy bytes from the scratch blob into a free-standing asset on `repo`.
    //    The graph for the destination repo records the index row under THIS repo
    //    and reaches the same workspace-global blob backend the scratch bytes live
    //    in, so download + upload share one backend.
    const graph = workspaceManager.getGraph(repo);
    const svc = graph.getAssetService?.();
    if (!svc) {
        throw new PromoteScratchError('STORAGE_UNAVAILABLE', 'Asset storage is not configured.');
    }
    const bytes = await svc.download(row.blobKey);
    const displayName = deriveAssetDisplayName(row.filename) || row.filename;
    const stored = await graph.uploadAsset('material', // ignored for ownership when `owner` is set
    'asset', // ignored
    row.filename, bytes, row.contentType, // preserve the file's MIME (image or doc)
    {
        owner: { type: 'folder', id: repo }, // free-standing, folder-owned
        uploaderUserId: String(user.id),
        name: displayName,
    });
    return {
        assetId: stored.assetId,
        url: stored.publicUrl,
        repo,
        filename: stored.filename,
        displayName,
    };
}
//# sourceMappingURL=scratch-asset-promote.js.map