var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { injectable } from 'tsyringe';
import { mkdirSync, writeFileSync, renameSync, rmSync, existsSync, readdirSync } from 'fs';
import { join, basename } from 'path';
import { createHash, randomUUID } from 'crypto';
import { getShepHomeDir } from './filesystem/shep-directory.service.js';
let AttachmentStorageService = class AttachmentStorageService {
    /** In-memory dedup index: sessionId -> Map<sha256, StoredAttachment> */
    dedupIndex = new Map();
    /**
     * Store a file buffer in the pending attachment directory within SHEP_HOME.
     * Returns existing record if SHA-256 matches (dedup within same session).
     */
    store(buffer, filename, mimeType, sessionId) {
        const hash = createHash('sha256').update(buffer).digest('hex');
        // Check dedup
        const sessionMap = this.dedupIndex.get(sessionId);
        if (sessionMap?.has(hash)) {
            return sessionMap.get(hash);
        }
        const sanitized = this.uniqueFilename(this.sanitizeFilename(filename), hash);
        const pendingDir = this.getPendingDir(sessionId);
        mkdirSync(pendingDir, { recursive: true });
        const filePath = join(pendingDir, sanitized);
        writeFileSync(filePath, buffer);
        const attachment = {
            id: randomUUID(),
            name: sanitized,
            size: BigInt(buffer.length),
            mimeType,
            path: filePath.replace(/\\/g, '/'),
            createdAt: new Date(),
            sha256: hash,
        };
        // Track for dedup
        if (!this.dedupIndex.has(sessionId)) {
            this.dedupIndex.set(sessionId, new Map());
        }
        this.dedupIndex.get(sessionId).set(hash, attachment);
        return attachment;
    }
    /**
     * Commit pending uploads: rename pending dir to feature slug dir,
     * update paths on all attachment records.
     */
    commit(sessionId, featureSlug) {
        const pendingDir = this.getPendingDir(sessionId);
        const slugDir = this.getSlugDir(featureSlug);
        if (!existsSync(pendingDir)) {
            return [];
        }
        // Rename atomically
        mkdirSync(this.getAttachmentsRoot(), { recursive: true });
        renameSync(pendingDir, slugDir);
        // Build updated attachment records
        const sessionMap = this.dedupIndex.get(sessionId);
        const attachments = [];
        if (sessionMap) {
            for (const stored of sessionMap.values()) {
                const newPath = join(slugDir, basename(stored.path));
                attachments.push({
                    id: stored.id,
                    name: stored.name,
                    size: stored.size,
                    mimeType: stored.mimeType,
                    path: newPath.replace(/\\/g, '/'),
                    createdAt: stored.createdAt,
                });
            }
            this.dedupIndex.delete(sessionId);
        }
        else {
            // Fallback: read files from slugDir
            const files = readdirSync(slugDir);
            for (const file of files) {
                const filePath = join(slugDir, file);
                attachments.push({
                    id: randomUUID(),
                    name: file,
                    size: BigInt(0),
                    mimeType: 'application/octet-stream',
                    path: filePath.replace(/\\/g, '/'),
                    createdAt: new Date(),
                });
            }
        }
        return attachments;
    }
    /**
     * Delete all attachments for a feature.
     */
    delete(featureSlug) {
        const slugDir = this.getSlugDir(featureSlug);
        if (existsSync(slugDir)) {
            rmSync(slugDir, { recursive: true, force: true });
        }
    }
    getAttachmentsRoot() {
        return join(getShepHomeDir(), 'attachments');
    }
    getPendingDir(sessionId) {
        return join(getShepHomeDir(), 'attachments', `pending-${sessionId}`);
    }
    getSlugDir(featureSlug) {
        return join(getShepHomeDir(), 'attachments', featureSlug);
    }
    /** Generate unique filename by appending content hash when name collides (e.g. clipboard "image.png"). */
    uniqueFilename(filename, hash) {
        const dot = filename.lastIndexOf('.');
        if (dot <= 0)
            return `${filename}-${hash.slice(0, 8)}`;
        return `${filename.slice(0, dot)}-${hash.slice(0, 8)}${filename.slice(dot)}`;
    }
    /** Strip path traversal and non-safe characters from filenames. */
    sanitizeFilename(filename) {
        // Remove path components, keep only the filename
        let name = basename(filename);
        // Remove path traversal
        name = name.replace(/\.\./g, '');
        // Replace non-alphanumeric chars (except dot, hyphen, underscore) with underscore
        name = name.replace(/[^a-zA-Z0-9._-]/g, '_');
        // Remove leading dots (hidden files)
        name = name.replace(/^\.+/, '');
        // Fallback if empty
        return name || 'unnamed';
    }
};
AttachmentStorageService = __decorate([
    injectable()
], AttachmentStorageService);
export { AttachmentStorageService };
