"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.matchesPattern = matchesPattern;
exports.hasReadAccess = hasReadAccess;
exports.hasWriteAccess = hasWriteAccess;
exports.getAccessDeniedMessage = getAccessDeniedMessage;
const path_1 = __importDefault(require("path"));
const log_1 = require("./log");
const rtws_1 = require("./rtws");
function isEncapsulatedTaskPath(targetPath) {
    const normalized = normalizeAccessControlPath(targetPath);
    // Matches: "foo.tsk", "foo.tsk/", "a/b/foo.tsk/x", etc.
    return /(^|\/)[^/]+\.tsk(\/|$)/.test(normalized);
}
function isMindsPath(targetPath) {
    const normalized = normalizeAccessControlPath(targetPath).replace(/^\/+/, '');
    return normalized === '.minds' || normalized.startsWith('.minds/');
}
function isMainDialogsPath(targetPath) {
    // Only deny `.dialogs/**` at rtws root; allow nested `foo/.dialogs/**` for dev rtws layouts.
    const normalized = normalizeAccessControlPath(targetPath).replace(/^\/+/, '');
    return normalized === '.dialogs' || normalized.startsWith('.dialogs/');
}
function normalizeAccessControlPath(targetPath) {
    const normalized = targetPath.replace(/\\/g, '/');
    return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
}
function normalizeFileExtName(raw) {
    return raw.trim().toLowerCase().replace(/^\.+/, '');
}
function extractFileExtName(targetPath) {
    const normalized = targetPath.replace(/\\/g, '/').replace(/\/+$/g, '');
    if (normalized === '' || normalized === '.')
        return undefined;
    const baseName = normalized.split('/').pop();
    if (!baseName || baseName === '.' || baseName === '..')
        return undefined;
    const dotIndex = baseName.lastIndexOf('.');
    if (dotIndex <= 0 || dotIndex === baseName.length - 1)
        return undefined;
    return normalizeFileExtName(baseName.slice(dotIndex + 1));
}
function hasFileExtAccess(fileExtName, whitelist, blacklist) {
    // Extension rules only apply to file-like paths with a detectable extension.
    if (!fileExtName)
        return true;
    for (const ext of blacklist ?? []) {
        if (normalizeFileExtName(ext) === fileExtName) {
            return false;
        }
    }
    const allow = whitelist ?? [];
    if (allow.length === 0) {
        return true;
    }
    for (const ext of allow) {
        if (normalizeFileExtName(ext) === fileExtName) {
            return true;
        }
    }
    return false;
}
function resolveRtwsRelativePath(targetPath) {
    const cwd = (0, rtws_1.domindsRtwsRootAbs)();
    const resolvedPath = path_1.default.resolve(cwd, targetPath);
    const relativePath = path_1.default.relative(cwd, resolvedPath);
    if (relativePath === '' || (!relativePath.startsWith('..') && !path_1.default.isAbsolute(relativePath))) {
        return relativePath;
    }
    return null;
}
/**
 * Directory-specific pattern matching for access control.
 * This function determines if a target path (file or directory) should be controlled
 * by a directory pattern. The pattern represents a directory scope.
 *
 * Supports:
 * - "*" matches any single directory/file name within a path segment
 * - "**" matches any number of directory levels (including zero)
 * - Exact directory matches
 *
 * Directory semantics:
 * - Pattern "src" matches "src/file.txt", "src/subdir/file.js", etc.
 * - Pattern "src" does NOT match "src-backup/file.txt" (prevents false positives)
 * - Pattern "src/tools" matches "src/tools/fs.ts" but not "src/other/file.ts"
 */
function matchesPattern(targetPath, dirPattern) {
    // Normalize paths - remove leading/trailing slashes, convert to forward slashes, handle empty paths
    const normalizedTarget = normalizeAccessControlPath(targetPath).replace(/^\/+|\/+$/g, '') || '.';
    let normalizedDirPattern = normalizeAccessControlPath(dirPattern).replace(/^\/+|\/+$/g, '') || '.';
    // Patterns ending in `/**` represent a directory scope and should match the directory itself too.
    // Example: `.minds/**` must match both `.minds` and `.minds/team.yaml`.
    while (normalizedDirPattern.endsWith('/**')) {
        normalizedDirPattern = normalizedDirPattern.slice(0, -3) || '.';
    }
    // Handle root directory pattern
    if (normalizedDirPattern === '.' || normalizedDirPattern === '') {
        return true; // Root pattern matches everything
    }
    // Handle exact match (target is exactly the directory or a file with same name as directory)
    if (normalizedDirPattern === normalizedTarget) {
        return true;
    }
    // Handle wildcard patterns
    if (normalizedDirPattern.includes('*')) {
        // For patterns like **/*secret*, check if any path segment contains "secret"
        const patternParts = normalizedDirPattern.split('/');
        const targetParts = normalizedTarget.split('/');
        // Special handling for **/* patterns - check if any target segment matches the final pattern
        if (normalizedDirPattern.includes('**/*') && patternParts.length >= 2) {
            const lastPatternPart = patternParts[patternParts.length - 1];
            if (lastPatternPart.includes('*')) {
                // Convert the last part to regex (e.g., *secret* -> .*secret.*)
                let segmentRegex = lastPatternPart
                    .replace(/[.+^${}()|[\]\\]/g, '\\$&')
                    .replace(/\*/g, '.*');
                segmentRegex = '^' + segmentRegex + '$';
                try {
                    const regex = new RegExp(segmentRegex);
                    // Check if any target segment matches
                    for (const segment of targetParts) {
                        if (regex.test(segment)) {
                            return true;
                        }
                    }
                }
                catch (err) {
                    log_1.log.warn(`Invalid regex pattern in segment matching: ${segmentRegex}`, err);
                    // Invalid regex, fall through to other matching
                }
            }
        }
        // Full path regex matching for complex patterns
        let regexPattern = normalizedDirPattern
            // Escape special regex characters except * and **
            .replace(/[.+^${}()|[\]\\]/g, '\\$&')
            // Handle ** first (matches any number of path segments including none)
            .replace(/\*\*/g, '___DOUBLESTAR___')
            // Handle single * (matches any characters within a single path segment, but not path separators)
            .replace(/\*/g, '[^/]*')
            // Replace ** placeholder with pattern that matches any path segments
            .replace(/___DOUBLESTAR___/g, '.*');
        // For directory semantics, the pattern should match:
        // 1. Exact path match
        // 2. Path that starts with pattern followed by a path separator
        regexPattern = '^' + regexPattern + '(?:/.*)?$';
        try {
            const regex = new RegExp(regexPattern);
            return regex.test(normalizedTarget);
        }
        catch (err) {
            log_1.log.warn(`Invalid regex pattern in path matching: ${regexPattern}`, err);
            // If regex is invalid, fall back to exact match
            return normalizedDirPattern === normalizedTarget;
        }
    }
    // For non-wildcard directory patterns, use proper directory semantics
    // Pattern should match if target is:
    // 1. Exactly the directory path
    // 2. A path that starts with the directory path followed by a path separator
    // Ensure we don't match partial directory names (e.g., "src" shouldn't match "src-backup")
    // by requiring either exact match or match followed by path separator
    const targetParts = normalizedTarget.split('/');
    const patternParts = normalizedDirPattern.split('/');
    // Pattern must be a prefix of target path segments
    if (patternParts.length > targetParts.length) {
        return false; // Pattern is deeper than target
    }
    // Check each pattern segment against corresponding target segment
    for (let i = 0; i < patternParts.length; i++) {
        if (patternParts[i] !== targetParts[i]) {
            return false;
        }
    }
    return true; // All pattern segments matched
}
/**
 * Check if a member has read access to a specific path.
 *
 * Access control logic:
 * 1. Check blacklist first (no_read_dirs) - if path matches any blacklist pattern, deny access
 * 2. Check whitelist (read_dirs) - if path matches any whitelist pattern, allow access
 * 3. Check extension blacklist (no_read_file_ext_names) - if extension matches, deny access
 * 4. Check extension whitelist (read_file_ext_names)
 * 5. If no whitelist patterns are defined for a dimension, allow access (default allow)
 * 6. If whitelist patterns exist but none match, deny access
 */
function hasReadAccess(member, targetPath) {
    // Ensure path is within rtws
    const relativePath = resolveRtwsRelativePath(targetPath);
    if (relativePath === null)
        return false;
    // Taskdocs (`*.tsk/`) are encapsulated and hard-denied for all general file tools.
    if (isEncapsulatedTaskPath(relativePath)) {
        return false;
    }
    // Main dialogs (`.dialogs/**`) are reserved runtime state.
    // Hard-denied for all general file tools at rtws root (but allowed under nested workspaces).
    if (isMainDialogsPath(relativePath)) {
        return false;
    }
    // Minds (`.minds/**`) is reserved rtws state.
    // It is hard-denied for general file tools; only dedicated `.minds/`-scoped tools (team_mgmt)
    // may bypass this via an internal-only flag.
    const isMinds = isMindsPath(relativePath);
    const allowMindsBypass = member.internal_allow_minds === true;
    if (isMinds && !allowMindsBypass) {
        return false;
    }
    // Check blacklist first (no_read_dirs)
    const blacklist = member.no_read_dirs || [];
    for (const pattern of blacklist) {
        if (matchesPattern(relativePath, pattern)) {
            return false; // Explicitly denied
        }
    }
    // Check whitelist (read_dirs)
    const whitelist = member.read_dirs || [];
    // Note: `.minds/**` is handled above as a hard deny (unless internal bypass is enabled).
    // If no whitelist is defined, allow access (after blacklist check)
    let directoryAllowed = whitelist.length === 0;
    if (!directoryAllowed) {
        // Check if path matches any whitelist pattern
        for (const pattern of whitelist) {
            if (matchesPattern(relativePath, pattern)) {
                directoryAllowed = true;
                break;
            }
        }
    }
    if (!directoryAllowed)
        return false;
    return hasFileExtAccess(extractFileExtName(relativePath), member.read_file_ext_names, member.no_read_file_ext_names);
}
/**
 * Check if a member has write access to a specific path.
 *
 * Access control logic:
 * 1. Check blacklist first (no_write_dirs) - if path matches any blacklist pattern, deny access
 * 2. Check whitelist (write_dirs) - if path matches any whitelist pattern, allow access
 * 3. Check extension blacklist (no_write_file_ext_names) - if extension matches, deny access
 * 4. Check extension whitelist (write_file_ext_names)
 * 5. If no whitelist patterns are defined for a dimension, allow access (default allow)
 * 6. If whitelist patterns exist but none match, deny access
 */
function hasWriteAccess(member, targetPath) {
    // Ensure path is within rtws
    const relativePath = resolveRtwsRelativePath(targetPath);
    if (relativePath === null)
        return false;
    // Taskdocs (`*.tsk/`) are encapsulated and hard-denied for all general file tools.
    if (isEncapsulatedTaskPath(relativePath)) {
        return false;
    }
    // Main dialogs (`.dialogs/**`) are reserved runtime state.
    // Hard-denied for all general file tools at rtws root (but allowed under nested workspaces).
    if (isMainDialogsPath(relativePath)) {
        return false;
    }
    // Minds (`.minds/**`) is reserved rtws state.
    // It is hard-denied for general file tools; only dedicated `.minds/`-scoped tools (team_mgmt)
    // may bypass this via an internal-only flag.
    const isMinds = isMindsPath(relativePath);
    const allowMindsBypass = member.internal_allow_minds === true;
    if (isMinds && !allowMindsBypass) {
        return false;
    }
    // Check blacklist first (no_write_dirs)
    const blacklist = member.no_write_dirs || [];
    for (const pattern of blacklist) {
        if (matchesPattern(relativePath, pattern)) {
            return false; // Explicitly denied
        }
    }
    // Check whitelist (write_dirs)
    const whitelist = member.write_dirs || [];
    // Note: `.minds/**` is handled above as a hard deny (unless internal bypass is enabled).
    // If no whitelist is defined, allow access (after blacklist check)
    let directoryAllowed = whitelist.length === 0;
    if (!directoryAllowed) {
        // Check if path matches any whitelist pattern
        for (const pattern of whitelist) {
            if (matchesPattern(relativePath, pattern)) {
                directoryAllowed = true;
                break;
            }
        }
    }
    if (!directoryAllowed)
        return false;
    return hasFileExtAccess(extractFileExtName(relativePath), member.write_file_ext_names, member.no_write_file_ext_names);
}
/**
 * Get an access denied error message for a specific operation and path.
 */
function getAccessDeniedMessage(operation, targetPath, language = 'en') {
    const lines = language === 'zh'
        ? [
            '❌ **访问被拒绝**',
            '',
            `- 操作：\`${operation}\``,
            `- 路径：\`${targetPath}\``,
            `- 代码：\`ACCESS_DENIED\``,
        ]
        : [
            '❌ **Access Denied**',
            '',
            `- Operation: \`${operation}\``,
            `- Path: \`${targetPath}\``,
            `- Code: \`ACCESS_DENIED\``,
        ];
    if (isEncapsulatedTaskPath(targetPath)) {
        lines.push('');
        if (language === 'zh') {
            lines.push(`- 说明：\`*.tsk/\` 是封装差遣牒。通用文件工具无法读/写/列目录/删除其中内容（硬编码无条件拒绝）。`);
            lines.push(`- 提示：新增章节请使用 \`do_mind\`；少量追加请使用 \`mind_more\`（默认 progress：\`mind_more({\"items\":[\"...\"]})\`）；改写已有整章请先用 \`recall_taskdoc\` 取得当前内容哈希，再使用带 \`previous_content_hash\` 的 \`change_mind\`（顶层示例：先 \`recall_taskdoc({\"selector\":\"progress\"})\`，再 \`change_mind({\"selector\":\"progress\",\"content\":\"...\",\"previous_content_hash\":\"crc32:...\"})\`；额外章节：先 \`recall_taskdoc({\"category\":\"<category>\",\"selector\":\"<selector>\"})\`，再 \`change_mind({\"category\":\"<category>\",\"selector\":\"<selector>\",\"content\":\"...\",\"previous_content_hash\":\"crc32:...\"})\`）。`);
            lines.push(`- 提示：读取章节或取得 content_hash 请使用函数工具 \`recall_taskdoc\`：顶层 \`recall_taskdoc({\"selector\":\"progress\"})\`；额外章节 \`recall_taskdoc({\"category\":\"<category>\",\"selector\":\"<selector>\"})\`。`);
        }
        else {
            lines.push(`- Note: \`*.tsk/\` is an encapsulated Taskdoc. It is hard-denied for all general file tools.`);
            lines.push(`- Hint: To create a section, use \`do_mind\`; for small append-only updates, use \`mind_more\` (defaults to progress: \`mind_more({\"items\":[\"...\"]})\`); for full-section replacements, call \`recall_taskdoc\` for the current content hash first, then use \`change_mind\` with \`previous_content_hash\` (top-level example: first \`recall_taskdoc({\"selector\":\"progress\"})\`, then \`change_mind({\"selector\":\"progress\",\"content\":\"...\",\"previous_content_hash\":\"crc32:...\"})\`; extra sections: first \`recall_taskdoc({\"category\":\"<category>\",\"selector\":\"<selector>\"})\`, then \`change_mind({\"category\":\"<category>\",\"selector\":\"<selector>\",\"content\":\"...\",\"previous_content_hash\":\"crc32:...\"})\`).`);
            lines.push(`- Hint: To read a section or get content_hash, use \`recall_taskdoc\`: top-level \`recall_taskdoc({\"selector\":\"progress\"})\`; extra section \`recall_taskdoc({\"category\":\"<category>\",\"selector\":\"<selector>\"})\`.`);
        }
    }
    if (isMindsPath(targetPath)) {
        lines.push('');
        if (language === 'zh') {
            lines.push(`- 说明：\`.minds/\` 是 rtws（运行时工作区）的“团队配置/记忆/资产”目录，通用文件工具无法读写（硬编码无条件拒绝）。`);
            lines.push(`- 提示：如果你是在管理“个人记忆”，请不要尝试直接读/写/列目录 \`.minds/memory/**\`（会被拒绝）。请使用个人记忆工具：\`add_personal_memory\` / \`replace_personal_memory\` / \`drop_personal_memory\` / \`clear_personal_memory\`。首次创建时直接 \`add_personal_memory\` 即可，目录会自动创建；\`path\` 不应包含你的成员 id。`);
            lines.push(`- 提示：如果你是在管理“团队共享记忆”，请使用团队共享记忆工具：\`add_team_memory\` / \`replace_team_memory\` / \`drop_team_memory\` / \`clear_team_memory\`。`);
            lines.push(`- 提示：若团队配置了 \`team_mgmt\` 工具集，请使用其中工具（\`team_mgmt_*\`）代管；若未配置或你不具备权限，请诉请具备 \`team_mgmt\` 权限的成员/团队管理员成员代管。`);
        }
        else {
            lines.push(`- Note: \`.minds/\` stores rtws (runtime workspace) team config/memory/assets and is hard-denied for general file tools.`);
            lines.push(`- Hint: If you're trying to manage **personal memory**, do not read/write/list \`.minds/memory/**\` directly (it will be denied). Use personal-memory tools: \`add_personal_memory\` / \`replace_personal_memory\` / \`drop_personal_memory\` / \`clear_personal_memory\`. For first-time setup, just call \`add_personal_memory\`—the directory is created automatically; \`path\` must not include your member id.`);
            lines.push(`- Hint: If you're trying to manage **team shared memory**, use: \`add_team_memory\` / \`replace_team_memory\` / \`drop_team_memory\` / \`clear_team_memory\`.`);
            lines.push(`- Hint: If your team configured the \`team_mgmt\` toolset, use its tools (\`team_mgmt_*\`); otherwise (or if you lack access), tellask a team-admin / a member with \`team_mgmt\` access to manage it for you.`);
        }
    }
    if (isMainDialogsPath(targetPath)) {
        lines.push('');
        if (language === 'zh') {
            lines.push(`- 说明：\`.dialogs/\` 是 rtws 根目录下的“对话/运行态持久化”目录，通用文件工具无法读写（硬编码无条件拒绝）。`);
            lines.push(`- 提示：如需排查 Dominds 本身的问题，请在子目录 rtws 下复现（例如 \`ux-rtws/.dialogs/\`），该目录不受此硬编码限制。`);
        }
        else {
            lines.push(`- Note: \`.dialogs/\` at the rtws root stores dialog/runtime persistence and is hard-denied for general file tools.`);
            lines.push(`- Hint: For Dominds debugging, reproduce in a nested rtws (e.g. \`ux-rtws/.dialogs/\`), which is not covered by this hard deny.`);
        }
    }
    if (language === 'zh') {
        lines.push(`- 说明：该路径可能命中目录权限（\`read_dirs/write_dirs/no_*_dirs\`）或扩展名权限（\`*_file_ext_names/no_*_file_ext_names\`）。`);
    }
    else {
        lines.push(`- Note: This path may be blocked by directory rules (\`read_dirs/write_dirs/no_*_dirs\`) or file-extension rules (\`*_file_ext_names/no_*_file_ext_names\`).`);
    }
    return lines.join('\n');
}
