import { BlockExporter, car, CIDPath, depthFirstWalker, naturalOrderWalker, SubgraphExporter, UnixFSExporter } from '@helia/car';
import { code as dagPbCode } from '@ipld/dag-pb';
import { createScalableCuckooFilter } from '@libp2p/utils';
import toBrowserReadableStream from 'it-to-browser-readablestream';
import { getOffsetAndLength } from "../utils/get-offset-and-length.js";
import { notAcceptableResponse, okRangeResponse } from '../utils/responses.js';
import { BasePlugin } from './plugin-base.js';
function getFilename({ cid, ipfsPath, query }) {
    if (query.filename != null) {
        return query.filename;
    }
    // convert context.ipfsPath to a filename. replace all / with _, replace prefix protocol with empty string
    const filename = ipfsPath
        .replace(/\/ipfs\//, '')
        .replace(/\/ipns\//, '')
        .replace(/\/+$/g, '')
        .replace(/\//g, '_');
    return `${filename}.car`;
}
function getDagScope({ query }) {
    const dagScope = query['dag-scope'];
    if (dagScope === 'all' || dagScope === 'entity' || dagScope === 'block') {
        return dagScope;
    }
    // entity-bytes implies entity scope
    if (query['entity-bytes']) {
        return 'entity';
    }
    return 'all';
}
/**
 * Accepts a `CID` and returns a `Response` with a body stream that is a CAR
 * of the `DAG` referenced by the `CID`.
 */
export class CarPlugin extends BasePlugin {
    id = 'car-plugin';
    canHandle(context) {
        if (context.byteRangeContext == null) {
            return false;
        }
        if (context.pathDetails == null) {
            return false;
        }
        return context.accept?.mimeType.startsWith('application/vnd.ipld.car') === true || context.query.format === 'car'; // application/vnd.ipld.car
    }
    async handle(context) {
        const { options, pathDetails, cid, query, accept, resource } = context;
        const order = accept?.options.order === 'dfs' || context.query['car-order'] === 'dfs' ? 'dfs' : 'unk';
        const duplicates = accept?.options.dups !== 'n' && context.query['car-dups'] !== 'n';
        if (pathDetails == null) {
            throw new Error('attempted to handle request for car with no path details');
        }
        // TODO: `@ipld/car` only supports CARv1
        if (accept?.options.version === '2' || context.query['car-version'] === '2') {
            return notAcceptableResponse(resource, 'Only CARv1 is supported');
        }
        const { getBlockstore, helia } = this.pluginOptions;
        context.reqFormat = 'car';
        context.query.download = true;
        context.query.filename = getFilename(context);
        const blockstore = getBlockstore(cid, context.resource, options?.session ?? true, options);
        const c = car({
            blockstore,
            getCodec: helia.getCodec,
            logger: helia.logger
        });
        const carExportOptions = {
            ...options,
            includeTraversalBlocks: true
        };
        if (!duplicates) {
            carExportOptions.blockFilter = createScalableCuckooFilter(1024);
        }
        if (pathDetails.ipfsRoots.length > 1) {
            carExportOptions.traversal = new CIDPath(pathDetails.ipfsRoots);
        }
        const dagScope = getDagScope(context);
        const target = pathDetails.terminalElement.cid ?? cid;
        if (dagScope === 'block') {
            carExportOptions.exporter = new BlockExporter();
        }
        else if (dagScope === 'entity') {
            // if its unixFS, we need to enumerate a directory, or get all/some blocks
            // for the entity, otherwise, use blockExporter
            if (target.code === dagPbCode) {
                const options = {
                    listingOnly: true
                };
                const slice = getOffsetAndLength(pathDetails.terminalElement, query['entity-bytes']?.toString());
                options.offset = slice.offset;
                options.length = slice.length;
                carExportOptions.exporter = new UnixFSExporter(options);
            }
            else {
                carExportOptions.exporter = new BlockExporter();
            }
        }
        else {
            carExportOptions.exporter = new SubgraphExporter({
                walker: order === 'dfs' ? depthFirstWalker() : naturalOrderWalker()
            });
        }
        context.byteRangeContext.setBody(toBrowserReadableStream(c.export(target, carExportOptions)));
        const response = okRangeResponse(context.resource, context.byteRangeContext.getBody('application/vnd.ipld.car; version=1'), {
            byteRangeContext: context.byteRangeContext,
            log: this.log
        });
        response.headers.set('content-type', context.byteRangeContext.getContentType() ?? `application/vnd.ipld.car; version=1; order=${order}; dups=${duplicates ? 'y' : 'n'}`);
        return response;
    }
}
//# sourceMappingURL=plugin-handle-car.js.map