"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
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;
};
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var SettingService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SettingService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const fsPromises = __importStar(require("fs/promises"));
const error_messages_1 = require("../constants/error-messages");
const user_entity_1 = require("../entities/user.entity");
const setting_repository_1 = require("../repository/setting.repository");
const file_1 = require("./file");
const setting_entity_1 = require("../entities/setting.entity");
const request_context_service_1 = require("./request-context.service");
const solid_registry_1 = require("../helpers/solid-registry");
const interfaces_1 = require("../interfaces");
const module_metadata_repository_1 = require("../repository/module-metadata.repository");
const common_2 = require("@nestjs/common");
const encryption_service_1 = require("./encryption.service");
let SettingService = SettingService_1 = class SettingService {
    constructor(fileService, pathBuilder, solidRegistry, repo, moduleMetadataRepo, requestContextService, userRepository) {
        this.fileService = fileService;
        this.pathBuilder = pathBuilder;
        this.solidRegistry = solidRegistry;
        this.repo = repo;
        this.moduleMetadataRepo = moduleMetadataRepo;
        this.requestContextService = requestContextService;
        this.userRepository = userRepository;
        this._logger = new common_2.Logger(SettingService_1.name);
        this.settings = [];
        this.settingsByKey = new Map();
        this.arrayKeysToSkip = new Set([
            'authenticationPasswordRegex',
            'authenticationPasswordRegexErrorMessage',
            'authenticationPasswordComplexityDescription',
        ]);
        const encKey = process.env.APP_ENCRYPTION_KEY;
        this.encryptionService = encKey ? new encryption_service_1.EncryptionService(encKey) : null;
        if (!encKey)
            this._logger.warn('APP_ENCRYPTION_KEY is not set — encrypted settings will not be decrypted');
    }
    async getSettingsFromDb() {
        const settings = await this.repo.find({ relations: ['user'] });
        return settings;
    }
    parseSettingValue(value, key) {
        try {
            return JSON.parse(value);
        }
        catch {
            if (value === 'true' || value === 'false') {
                return value === 'true';
            }
            if (!isNaN(Number(value)) && value.trim() !== '') {
                return Number(value);
            }
            if (!this.arrayKeysToSkip.has(key) && value.includes(',')) {
                return value.split(',').map(item => item.trim());
            }
            return value;
        }
    }
    buildSettingLabel(key) {
        return key
            .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
            .replace(/[-_]+/g, ' ')
            .replace(/\s+/g, ' ')
            .trim()
            .replace(/\b\w/g, (match) => match.toUpperCase());
    }
    inferControlType(setting) {
        if (setting.controlType) {
            return setting.controlType;
        }
        if (setting.options?.length) {
            return 'selectionStatic';
        }
        if (typeof setting.value === 'boolean') {
            return 'boolean';
        }
        if (typeof setting.value === 'number') {
            return 'numeric';
        }
        return 'shortText';
    }
    toAdminSettingDefinition(setting) {
        return {
            ...setting,
            label: setting.label ?? this.buildSettingLabel(setting.key),
            controlType: this.inferControlType(setting),
            editable: setting.level === interfaces_1.SettingLevel.SystemAdminEditable,
        };
    }
    getAllSettingsFromProviders() {
        const allSettingsProviders = this.solidRegistry.getSettingsProviders();
        const settings = [];
        const seenKeys = new Map();
        for (const wrapper of allSettingsProviders) {
            const instance = wrapper.instance;
            for (const setting of instance.getSettings()) {
                const existing = seenKeys.get(setting.key);
                if (existing) {
                    throw new Error(`Duplicate setting key detected: ${setting.key}. ` +
                        `First: ${JSON.stringify(existing.setting)} from provider: ${existing.providerName ?? "unknown"}. ` +
                        `Second: ${JSON.stringify(setting)} from provider: ${wrapper.name ?? "unknown"}.`);
                }
                seenKeys.set(setting.key, { setting, providerName: wrapper.name });
                settings.push(setting);
            }
        }
        return settings;
    }
    getAllSettings() {
        return this.settings;
    }
    async updateSettingsCache() {
        this._logger.debug(`updating settings cache...`);
        const settingsFromDb = await this.getSettingsFromDb();
        const settingsFromProviders = this.getAllSettingsFromProviders();
        const settingsFromDbByKey = new Map(settingsFromDb.map(setting => [setting.key, setting]));
        this.settings = settingsFromProviders.map(setting => {
            const settingFromDb = settingsFromDbByKey.get(setting.key);
            const valueFromDb = settingFromDb?.value;
            if (settingFromDb?.key && valueFromDb !== undefined && valueFromDb !== null) {
                let rawValue = valueFromDb;
                if (settingFromDb.encrypted && this.encryptionService) {
                    try {
                        rawValue = this.encryptionService.decrypt(rawValue);
                    }
                    catch {
                        this._logger.warn(`Failed to decrypt setting "${setting.key}" — using raw value`);
                    }
                }
                const parsedValue = typeof rawValue === 'string' ? this.parseSettingValue(rawValue, settingFromDb.key) : rawValue;
                return { ...setting, value: parsedValue };
            }
            return setting;
        });
        this.settingsByKey = new Map(this.settings.map(setting => [setting.key, setting]));
    }
    isDisallowedSettingKey(key, settingLevelsToDisallow) {
        if (!key) {
            return false;
        }
        const setting = this.settingsByKey.get(key);
        return setting ? settingLevelsToDisallow.includes(setting.level) : false;
    }
    async seedSystemAdminEditableAndAboveSettings() {
        const saEditableAndAbove = this.getAllSettingsFromProviders().filter(i => [interfaces_1.SettingLevel.SystemAdminEditable, interfaces_1.SettingLevel.InternalUser].includes(i.level));
        const existingSettingsFromDb = await this.getSettingsFromDb();
        const existingSettingsFromDbByKey = new Map(existingSettingsFromDb.map(setting => [setting.key, setting]));
        const settingsToMutate = [];
        for (const { key, value, level, moduleName, encrypted } of saEditableAndAbove) {
            const moduleMetadata = await this.moduleMetadataRepo.findOneBy({ name: moduleName });
            if (!existingSettingsFromDbByKey.has(key)) {
                const setting = new setting_entity_1.Setting();
                setting.key = key;
                setting.level = level;
                setting.encrypted = !!encrypted;
                if (moduleMetadata)
                    setting.moduleMetadata = moduleMetadata;
                let rawValue;
                if (typeof value === 'boolean') {
                    rawValue = value.toString();
                }
                else if (Array.isArray(value)) {
                    rawValue = value.join(',');
                }
                else if (value === null || value === undefined) {
                    rawValue = null;
                }
                else {
                    rawValue = String(value);
                }
                if (encrypted && this.encryptionService && rawValue !== null) {
                    rawValue = this.encryptionService.encrypt(rawValue);
                }
                setting.value = rawValue;
                settingsToMutate.push(setting);
            }
            else {
                const setting = existingSettingsFromDbByKey.get(key);
                setting.level = level;
                setting.encrypted = !!encrypted;
                if (moduleMetadata)
                    setting.moduleMetadata = moduleMetadata;
                settingsToMutate.push(setting);
            }
        }
        if (settingsToMutate.length > 0) {
            await this.repo.save(settingsToMutate);
        }
    }
    async getNonEncryptedSystemAdminReadonlyAndAboveSettings() {
        const activeUser = this.requestContextService.getActiveUser();
        const hasViewEncryptedPermission = !!activeUser?.permissions?.includes('settings:view_encrypted');
        const data = this.settings
            .filter(i => i.level !== "system-env")
            .filter(i => hasViewEncryptedPermission || !i.encrypted)
            .filter((setting) => [interfaces_1.SettingLevel.SystemAdminReadonly, interfaces_1.SettingLevel.SystemAdminEditable].includes(setting.level))
            .map((setting) => this.toAdminSettingDefinition(setting))
            .sort((left, right) => {
            const groupCompare = (left.group ?? '').localeCompare(right.group ?? '');
            if (groupCompare !== 0) {
                return groupCompare;
            }
            const leftSort = left.sortOrder ?? Number.MAX_SAFE_INTEGER;
            const rightSort = right.sortOrder ?? Number.MAX_SAFE_INTEGER;
            if (leftSort !== rightSort) {
                return leftSort - rightSort;
            }
            return left.label.localeCompare(right.label);
        });
        return { data };
    }
    async updateSettings(settings = [], uploadedFiles = []) {
        const activeUser = this.requestContextService.getActiveUser();
        const userId = activeUser?.sub;
        const restrictedKeys = new Set();
        settings.forEach(setting => {
            if (this.isDisallowedSettingKey(setting.key, [interfaces_1.SettingLevel.SystemEnv, interfaces_1.SettingLevel.SystemAdminReadonly])) {
                restrictedKeys.add(setting.key);
            }
        });
        uploadedFiles.forEach(file => {
            if (this.isDisallowedSettingKey(file.fieldname, [interfaces_1.SettingLevel.SystemEnv, interfaces_1.SettingLevel.SystemAdminReadonly])) {
                restrictedKeys.add(file.fieldname);
            }
        });
        if (restrictedKeys.size > 0) {
            throw new common_1.BadRequestException(`${error_messages_1.ERROR_MESSAGES.FORBIDDEN}: Settings ${Array.from(restrictedKeys).join(', ')} cannot be modified.`);
        }
        const existingSettings = await this.getSettingsFromDb();
        const settingsToUpdate = [];
        const settingsToCreate = [];
        let user = null;
        const hasUserSettings = settings.some(dto => dto.type === 'user');
        if (hasUserSettings && userId) {
            user = await this.userRepository.findOne({ where: { id: userId } });
        }
        if (uploadedFiles?.length) {
            for (const file of uploadedFiles) {
                const settingKey = file.fieldname;
                const relativeFileName = `${file.filename}-${file.originalname}`;
                const fileContent = await fsPromises.readFile(file.path);
                const storagePath = this.pathBuilder.build(relativeFileName);
                const fileUrl = await this.fileService.write(storagePath, fileContent, { contentType: file.mimetype });
                await fsPromises.unlink(file.path);
                const matchedDto = settings.find(dto => dto.key === settingKey);
                const settingType = matchedDto?.type ?? 'system';
                const existingSetting = existingSettings.find(s => s.key === settingKey);
                if (existingSetting) {
                    existingSetting.value = fileUrl;
                    existingSetting.type = settingType;
                    settingsToUpdate.push(existingSetting);
                }
                else {
                    const newSetting = new setting_entity_1.Setting();
                    newSetting.key = settingKey;
                    newSetting.value = fileUrl;
                    newSetting.type = settingType;
                    if (settingType === 'user' && user) {
                        newSetting.user = user;
                    }
                    settingsToCreate.push(newSetting);
                }
            }
        }
        for (const settingDto of settings) {
            if (uploadedFiles?.some(file => file.fieldname === settingDto.key)) {
                continue;
            }
            const key = settingDto.key;
            const rawValue = settingDto.value;
            let value = rawValue === null || rawValue === undefined ? null : String(rawValue);
            const settingType = settingDto.type ?? 'system';
            const definition = this.settingsByKey.get(key);
            const shouldEncrypt = !!definition?.encrypted && this.encryptionService !== null && value !== null;
            if (shouldEncrypt) {
                value = this.encryptionService.encrypt(value);
            }
            const existingSetting = existingSettings.find(s => s.key === key);
            if (existingSetting) {
                existingSetting.value = value;
                existingSetting.type = settingType;
                existingSetting.encrypted = shouldEncrypt;
                settingsToUpdate.push(existingSetting);
            }
            else {
                const newSetting = new setting_entity_1.Setting();
                newSetting.key = key;
                newSetting.value = value;
                newSetting.type = settingType;
                newSetting.encrypted = shouldEncrypt;
                if (settingType === 'user' && user) {
                    newSetting.user = user;
                }
                settingsToCreate.push(newSetting);
            }
        }
        if (settingsToUpdate.length > 0) {
            await this.repo.save(settingsToUpdate);
        }
        if (settingsToCreate.length > 0) {
            await this.repo.save(settingsToCreate);
        }
        await this.updateSettingsCache();
        return [...settingsToUpdate, ...settingsToCreate];
    }
    getConfigValue(settingKey) {
        const cachedSetting = this.settingsByKey.get(settingKey);
        if (cachedSetting) {
            return cachedSetting.value;
        }
        return null;
    }
    async getMcpUrl(getMcpUrlDto, solidRequestContext = {}) {
        const { showHeader, inListView } = getMcpUrlDto;
        const mcpEnabled = this.getConfigValue('mcpEnabled');
        if (mcpEnabled === 'false' || mcpEnabled === false) {
            throw new common_1.ForbiddenException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
        }
        if (solidRequestContext.activeUser) {
            const permissionNames = ["SettingController.getMcpUrl"];
            const matchingPermssions = solidRequestContext.activeUser.permissions.filter((p) => permissionNames.includes(p));
            const hasPermission = matchingPermssions.length > 0;
            if (!hasPermission) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
            }
        }
        const apiKey = this.getConfigValue('mcpApiKey');
        const userId = solidRequestContext.activeUser.sub;
        const mcpServerUrl = this.getConfigValue('mcpServerUrl');
        return { mcpUrl: `${mcpServerUrl}/static/frontend.html?solidx-mcp-api-key=${apiKey}&solidx-user-id=${userId}&solidx-show-header=${showHeader}&solidx-in-list-view=${inListView}` };
    }
};
exports.SettingService = SettingService;
exports.SettingService = SettingService = SettingService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, common_1.Inject)(file_1.FILE_SERVICE)),
    __param(1, (0, common_1.Inject)(file_1.FILE_STORAGE_PATH_BUILDER)),
    __param(6, (0, typeorm_1.InjectRepository)(user_entity_1.User)),
    __metadata("design:paramtypes", [Object, Object, solid_registry_1.SolidRegistry,
        setting_repository_1.SettingRepository,
        module_metadata_repository_1.ModuleMetadataRepository,
        request_context_service_1.RequestContextService,
        typeorm_2.Repository])
], SettingService);
//# sourceMappingURL=setting.service.js.map