import { createHash, randomUUID } from "node:crypto";
import path from "node:path";
import { badRequest, forbidden, unprocessable } from "../errors.js";
const MAX_SEGMENT_LENGTH = 120;
function sanitizeSegment(value) {
    const cleaned = value
        .trim()
        .replace(/[^a-zA-Z0-9._-]+/g, "_")
        .replace(/_{2,}/g, "_")
        .replace(/^_+|_+$/g, "");
    if (!cleaned)
        return "file";
    return cleaned.slice(0, MAX_SEGMENT_LENGTH);
}
function normalizeNamespace(namespace) {
    const normalized = namespace
        .split("/")
        .map((entry) => entry.trim())
        .filter((entry) => entry.length > 0)
        .map((entry) => sanitizeSegment(entry));
    if (normalized.length === 0)
        return "misc";
    return normalized.join("/");
}
function splitFilename(filename) {
    if (!filename)
        return { stem: "file", ext: "" };
    const base = path.basename(filename).trim();
    if (!base)
        return { stem: "file", ext: "" };
    const extRaw = path.extname(base);
    const stemRaw = extRaw ? base.slice(0, base.length - extRaw.length) : base;
    const stem = sanitizeSegment(stemRaw);
    const ext = extRaw
        .toLowerCase()
        .replace(/[^a-z0-9.]/g, "")
        .slice(0, 16);
    return {
        stem,
        ext,
    };
}
function ensureCompanyPrefix(orgId, objectKey) {
    const expectedPrefix = `${orgId}/`;
    if (!objectKey.startsWith(expectedPrefix)) {
        throw forbidden("Object does not belong to organization");
    }
    if (objectKey.includes("..")) {
        throw badRequest("Invalid object key");
    }
}
function hashBuffer(input) {
    return createHash("sha256").update(input).digest("hex");
}
function buildObjectKey(orgId, namespace, originalFilename) {
    const ns = normalizeNamespace(namespace);
    const now = new Date();
    const year = String(now.getUTCFullYear());
    const month = String(now.getUTCMonth() + 1).padStart(2, "0");
    const day = String(now.getUTCDate()).padStart(2, "0");
    const { stem, ext } = splitFilename(originalFilename);
    const suffix = randomUUID();
    const filename = `${suffix}-${stem}${ext}`;
    return `${orgId}/${ns}/${year}/${month}/${day}/${filename}`;
}
function assertPutFileInput(input) {
    if (!input.orgId || input.orgId.trim().length === 0) {
        throw unprocessable("orgId is required");
    }
    if (!input.namespace || input.namespace.trim().length === 0) {
        throw unprocessable("namespace is required");
    }
    if (!input.contentType || input.contentType.trim().length === 0) {
        throw unprocessable("contentType is required");
    }
    if (!(input.body instanceof Buffer)) {
        throw unprocessable("body must be a Buffer");
    }
    if (input.body.length <= 0) {
        throw unprocessable("File is empty");
    }
}
export function createStorageService(provider) {
    return {
        provider: provider.id,
        async putFile(input) {
            assertPutFileInput(input);
            const objectKey = buildObjectKey(input.orgId, input.namespace, input.originalFilename);
            const byteSize = input.body.length;
            const contentType = input.contentType.trim().toLowerCase();
            await provider.putObject({
                objectKey,
                body: input.body,
                contentType,
                contentLength: byteSize,
            });
            return {
                provider: provider.id,
                objectKey,
                contentType,
                byteSize,
                sha256: hashBuffer(input.body),
                originalFilename: input.originalFilename,
            };
        },
        async getObject(orgId, objectKey) {
            ensureCompanyPrefix(orgId, objectKey);
            return provider.getObject({ objectKey });
        },
        async headObject(orgId, objectKey) {
            ensureCompanyPrefix(orgId, objectKey);
            return provider.headObject({ objectKey });
        },
        async deleteObject(orgId, objectKey) {
            ensureCompanyPrefix(orgId, objectKey);
            await provider.deleteObject({ objectKey });
        },
    };
}
//# sourceMappingURL=service.js.map