/**
 * Delete-time asset impact (assets-referenced-not-owned, Phase 2).
 *
 * Deleting an entry never destroys assets — but it CAN orphan them: an asset
 * referenced solely by the entry being deleted has no other home in any body.
 * The delete prompt offers exactly those for optional deletion. This service
 * computes the offer set:
 *
 *   - The entry's embedded assets (live editor body when open, else disk),
 *     restricted to index-tracked rows (only those are deletable by id).
 *   - `solelyReferenced` — NO other entry in the WORKSPACE references the
 *     asset. The scan is LIVE-RECONCILED (eng review D1): for entries with an
 *     open editor doc, the live Y.Doc body REPLACES the disk body (a reference
 *     just added in an editor blocks the offer; one just removed enables it).
 *     Disk frontmatter is always scanned (not editable live). The scan covers
 *     ALL repos — including ones the caller can't read — but reveals only the
 *     boolean (shown as "used elsewhere", never where).
 *   - `canDelete` — per-asset permission: write access to the ASSET's folder
 *     plus the workspace-store uploader/owner rule. Embedding an asset never
 *     implies delete rights.
 *
 * Fail-keep: the scan is budgeted (eng review: cap/timeout, never delete on
 * partial reference info). On budget overrun `partial` is true and every
 * asset reports solelyReferenced=false — the prompt then offers nothing.
 */
import { embeddedAssetIds } from '../core/embeds.js';
import { publicUrlForAssetId, getMediaType } from './assets/index.js';
import * as access from './access-control.js';
import { assetDeleteDenialByAssetId } from './asset-delete-authz.js';
import { scanWorkspaceAssetReferences, DEFAULT_SCAN_BUDGET_MS } from './asset-reference-scan.js';
export async function computeAssetDeleteImpact(opts) {
    const { workspaceManager: wm, authService, user, repo, entityType, entityId } = opts;
    const budgetMs = opts.budgetMs ?? DEFAULT_SCAN_BUDGET_MS;
    const graph = wm.getGraph(repo);
    const entity = graph.get(entityType, entityId); // throws → caller maps to 404
    // The entry's OWN embeds: live editor body when open (the user may have
    // just pasted an image that isn't on disk yet), else the disk body.
    const ownBody = graph.getLiveBody(entityType, entityId) ?? entity.document.content;
    const targetIds = embeddedAssetIds(ownBody, (entity.document.frontmatter ?? {}));
    const index = wm.getAssetIndex?.();
    if (!index || targetIds.size === 0)
        return { assets: [], partial: false };
    // Only index-tracked assets are offerable — delete-by-id needs a row.
    // (A legacy un-indexed URL in the body has no addressable asset.)
    const rows = [...targetIds]
        .map(id => index.get(id))
        .filter((r) => r != null);
    if (rows.length === 0)
        return { assets: [], partial: false };
    // ── Workspace reference scan (live-reconciled, budgeted) ──
    // The walk itself — repo iteration, live reconcile, budget, live-only-doc
    // sweep — is shared with the P3 loose-filter index (asset-reference-scan).
    // Here we only need: is any TARGET asset referenced by an entry OTHER than
    // the one being deleted? Early-exit once every target is proven shared.
    const targetIdSet = new Set(rows.map(r => r.assetId));
    const referencedElsewhere = new Set();
    const { partial } = await scanWorkspaceAssetReferences(wm, (ids) => {
        for (const id of ids)
            if (targetIdSet.has(id))
                referencedElsewhere.add(id);
    }, {
        budgetMs,
        skipKey: `${repo}/${entityType}/${entityId}`,
        stopWhen: () => referencedElsewhere.size === targetIdSet.size,
    });
    // ── Per-asset permission gate ──
    const assets = rows.map(row => {
        let canDelete = true;
        let denialReason = null;
        if (!access.canWriteRepo(user, row.repo, wm, authService)) {
            canDelete = false;
            denialReason = "You don't have edit access to the folder this file lives in.";
        }
        else {
            denialReason = assetDeleteDenialByAssetId(index, wm.getRepoConfig(row.repo), user, row.assetId);
            canDelete = denialReason === null;
        }
        return {
            assetId: row.assetId,
            filename: row.filename,
            name: row.name ?? row.filename,
            url: publicUrlForAssetId(row.assetId, row.filename),
            mediaType: getMediaType(row.contentType, row.filename),
            // The home repo is needed only to DELETE (by-id route); revealing it on
            // a non-deletable row would leak the name of a folder the caller may
            // not even read (no other surface does — the serve route 404s).
            repo: canDelete ? row.repo : '',
            // Fail-keep: a partial scan offers nothing.
            solelyReferenced: !partial && !referencedElsewhere.has(row.assetId),
            canDelete,
            denialReason,
        };
    });
    return { assets, partial };
}
//# sourceMappingURL=asset-delete-impact.js.map