import { Type } from '@sinclair/typebox';
import { access as fsAccess, readdir as fsReaddir, stat as fsStat } from 'fs/promises';
import nodePath from 'path';
import { resolveToCwd } from '../../shared/files/path-utils.js';
import { LS } from '../../tool-names.js';
import { withAbort } from '../../utils/abort.js';
import { DEFAULT_MAX_CHARS, formatSize, truncateHead } from '../../utils/truncate.js';
import { enforceFilesystemPolicy } from '../permissions/filesystem-policy.js';
import { requirePermissionPolicy } from '../permissions/require-policy.js';
const lsSchema = Type.Object({
    path: Type.Optional(Type.String({ description: 'Directory to list (default: current directory)' })),
    limit: Type.Optional(Type.Number({ description: 'Maximum number of entries to return (default: 500)' }))
});
const DEFAULT_LIMIT = 500;
const defaultLsOperations = {
    exists: async (p) => {
        try {
            await fsAccess(p);
            return true;
        }
        catch {
            return false;
        }
    },
    stat: (p) => fsStat(p),
    readdir: (p) => fsReaddir(p)
};
export function createLsTool(cwd, options) {
    const ops = options?.operations ?? defaultLsOperations;
    return {
        name: LS,
        label: LS,
        isReadOnly: true,
        description: `List directory contents. Returns entries sorted alphabetically, with '/' suffix for directories. Includes dotfiles. Output is truncated to ${DEFAULT_LIMIT} entries or ${DEFAULT_MAX_CHARS} characters (whichever is hit first).`,
        parameters: lsSchema,
        execute: async (_toolCallId, { path, limit }, signal, _onUpdate, ctx) => {
            return withAbort(signal, async () => {
                const dirPath = resolveToCwd(path || '.', cwd);
                const effectiveLimit = limit ?? DEFAULT_LIMIT;
                const permissionPolicy = requirePermissionPolicy(ctx, LS);
                await enforceFilesystemPolicy({
                    path: dirPath,
                    policy: permissionPolicy.filesystem,
                    access: 'read',
                    sessionState: ctx?.sessionState,
                    approvalGrants: ctx?.approvalGrants?.filesystem
                });
                if (!(await ops.exists(dirPath))) {
                    throw new Error(`Path not found: ${dirPath}`);
                }
                const stat = await ops.stat(dirPath);
                if (!stat.isDirectory()) {
                    throw new Error(`Not a directory: ${dirPath}`);
                }
                let entries;
                try {
                    entries = await ops.readdir(dirPath);
                }
                catch (e) {
                    throw new Error(`Cannot read directory: ${e.message}`);
                }
                entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
                const results = [];
                let entryLimitReached = false;
                for (const entry of entries) {
                    if (results.length >= effectiveLimit) {
                        entryLimitReached = true;
                        break;
                    }
                    const fullPath = nodePath.join(dirPath, entry);
                    let suffix = '';
                    try {
                        const entryStat = await ops.stat(fullPath);
                        if (entryStat.isDirectory()) {
                            suffix = '/';
                        }
                    }
                    catch {
                        continue;
                    }
                    results.push(entry + suffix);
                }
                if (results.length === 0) {
                    return {
                        content: [{ type: 'text', text: '(empty directory)' }],
                        details: undefined
                    };
                }
                const rawOutput = results.join('\n');
                const truncation = truncateHead(rawOutput);
                let output = truncation.content;
                const details = {};
                const notices = [];
                if (entryLimitReached) {
                    notices.push(`${effectiveLimit} entries limit reached. Use limit=${effectiveLimit * 2} for more`);
                    details.entryLimitReached = effectiveLimit;
                }
                if (truncation.truncated) {
                    notices.push(`${formatSize(DEFAULT_MAX_CHARS)} limit reached`);
                    details.truncation = truncation;
                }
                if (notices.length > 0) {
                    output += `\n\n[${notices.join('. ')}]`;
                }
                return {
                    content: [{ type: 'text', text: output }],
                    details: Object.keys(details).length > 0 ? details : undefined
                };
            });
        }
    };
}
//# sourceMappingURL=ls.js.map