import { createHash } from 'node:crypto';
import { open, readFile, writeFile, mkdir } from 'node:fs/promises';
import os from 'node:os';
import { pipeline } from 'node:stream/promises';
import { basename, extname, isAbsolute, join, relative, resolve } from 'node:path';
import { resolveCodoriHome } from './config.js';
const hashSegment = (value) => createHash('sha256').update(value).digest('hex').slice(0, 16);
const sanitizeFilename = (value) => {
    const normalized = basename(value).trim();
    return normalized || 'attachment';
};
const createFileNameCandidate = (directory, filename, index) => {
    const extension = extname(filename);
    const base = extension ? filename.slice(0, -extension.length) : filename;
    return join(directory, index === 0 ? filename : `${base}-${index}${extension}`);
};
const attachmentMetadataPath = (filePath) => `${filePath}.metadata.json`;
export const resolveAttachmentsRootDir = (rootDir) => resolve(rootDir ?? join(resolveCodoriHome(os.homedir()), 'attachments'));
export const resolveProjectAttachmentsDir = (projectPath, rootDir) => join(resolveAttachmentsRootDir(rootDir), hashSegment(projectPath));
export const resolveThreadAttachmentsDir = (projectPath, threadId, rootDir) => join(resolveProjectAttachmentsDir(projectPath, rootDir), hashSegment(threadId));
export const isPathInsideDirectory = (targetPath, directory) => {
    const relativePath = relative(directory, targetPath);
    return relativePath === '' || (!relativePath.startsWith('..') && !isAbsolute(relativePath));
};
export const writeAttachmentMetadata = async (filePath, metadata) => {
    await writeFile(attachmentMetadataPath(filePath), `${JSON.stringify(metadata, null, 2)}\n`, 'utf8');
};
export const readAttachmentMetadata = async (filePath) => {
    try {
        const source = await readFile(attachmentMetadataPath(filePath), 'utf8');
        const parsed = JSON.parse(source);
        if (typeof parsed === 'object'
            && parsed !== null
            && 'mediaType' in parsed
            && (typeof parsed.mediaType === 'string' || parsed.mediaType === null)) {
            return {
                mediaType: parsed.mediaType
            };
        }
        return null;
    }
    catch {
        return null;
    }
};
export const createThreadAttachmentTarget = async (input) => {
    const directory = resolveThreadAttachmentsDir(input.projectPath, input.threadId, input.rootDir);
    await mkdir(directory, { recursive: true });
    const filename = sanitizeFilename(input.filename);
    for (let index = 0; index < 10_000; index += 1) {
        const candidate = createFileNameCandidate(directory, filename, index);
        try {
            const handle = await open(candidate, 'wx');
            return {
                filePath: candidate,
                handle
            };
        }
        catch (error) {
            if (error.code === 'EEXIST') {
                continue;
            }
            throw error;
        }
    }
    throw new Error(`Failed to allocate a unique attachment path for ${filename}.`);
};
export const persistThreadAttachmentStream = async (input) => {
    const target = await createThreadAttachmentTarget(input);
    try {
        await pipeline(input.stream, target.handle.createWriteStream());
        await writeAttachmentMetadata(target.filePath, {
            mediaType: input.mediaType
        });
        return {
            filename: basename(target.filePath),
            mediaType: input.mediaType,
            path: target.filePath
        };
    }
    finally {
        await target.handle.close().catch(() => { });
    }
};
