"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 __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;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const path = __importStar(require("path"));
const schemas_1 = require("../schemas/schemas");
const utils_1 = require("#sfvc_src/utils/utils");
/** @inheritdoc */
class FileSystemService {
    /**
     * Constructs the file system service. Automatically registers this FileSystemService object with the settings service.
     * @param settings - An instance of the settings service. NOTE: This constructor automatically calls `settings.registerFilesystem(this)`
     * @param logger  - The logger to use with this filesystem service.
     * @param data_directory  - The data directory associated with this filesystem service.
     */
    constructor(settings, logger, data_directory, fs) {
        this.settings = settings;
        this.logger = logger;
        this.data_directory = data_directory;
        this.fs = fs;
        settings.registerFilesystem(this);
        this.settings_path = path.join(this.data_directory, "sfvc_settings.json");
    }
    /** @inheritdoc */
    async getPluginManifest(plugin_basename) {
        const manifestPath = await this.getPluginFilepath(plugin_basename, "manifest.json");
        const content = await this.fs.readFile(manifestPath, "utf-8");
        const parsed = JSON.parse(content);
        return schemas_1.PluginManifestSchema.parse(parsed);
    }
    /**
     * Gets the path for a plugin file
     * @param plugin_basename - The basename of the plugin (folder name).
     * @param filename The file to retrieve
     * @returns The path
     */
    async getPluginFilepath(plugin_basename, filename) {
        return path.join(await this.settings.getPluginsDirectory(), plugin_basename, filename);
    }
    /** @inheritdoc */
    async getJobLog() {
        const logPath = path.join(this.data_directory, 'job.log');
        return await this.fs.readFile(logPath, "utf-8");
    }
    /** @inheritdoc */
    async getPlugin(name, type) {
        const manifest = await this.getPluginManifest(name);
        if (type && manifest.type !== type)
            throw Error("Plugin is of incorrect type");
        const pluginPath = await this.getPluginFilepath(name, "index.js");
        const module = await import(path.resolve(pluginPath));
        const { activate } = module;
        return { activate: activate, manifest: manifest };
    }
    /** @inheritdoc */
    async getGatheredTrait(capture_basename, event_basename, trait_id) {
        const gathered = (await this.getEvent(capture_basename, event_basename)).gathered_traits.get(trait_id);
        if (gathered) {
            return gathered;
        }
        throw new Error(`No trait matching ${trait_id} found`);
    }
    /** @inheritdoc */
    async getEvent(capture_basename, event_basename) {
        const basePath = path.join(await this.settings.getCapturesDirectory(), capture_basename, event_basename);
        const event_manifest_raw = JSON.parse(await this.fs.readFile(path.join(basePath, "event.json"), "utf-8"));
        const event_manifest = schemas_1.EventManifestSchema.parse(event_manifest_raw);
        const results_raw = await this.fs.readFile(path.join(basePath, "results.json"), "utf-8");
        const results = new Map(JSON.parse(results_raw));
        return { manifest: event_manifest, gathered_traits: results };
    }
    async ensureCaptureExists(capture_basename) {
        // Create capture dir, skip otherwise
        const captureDir = path.join(await this.settings.getCapturesDirectory(), capture_basename);
        await this.fs.mkdir(captureDir, { recursive: true });
        return captureDir;
    }
    /** @inheritdoc */
    async writeEvent(capture_basename, results, manifest) {
        // Ensure capture folder exists, create otherwise
        const captureDir = await this.ensureCaptureExists(capture_basename);
        // Create a timestamped event subfolder
        const event_basename = (0, utils_1.format_string)((await this.settings.getSettings()).event_format, manifest.event_id);
        const eventDir = path.join(captureDir, event_basename);
        await this.fs.mkdir(eventDir, { recursive: true });
        // Write results to disk
        await this.fs.writeFile(path.join(eventDir, `results.json`), JSON.stringify(Array.from(results.entries())), 'utf-8');
        // Write event manifest to disk
        await this.fs.writeFile(path.join(eventDir, `event.json`), JSON.stringify(manifest), 'utf-8');
        this.logger.info(`written event to: ${event_basename}`);
        return event_basename;
    }
    /** @inheritdoc */
    async writeCapture(job, additional_data) {
        const capture_basename = (0, utils_1.format_string)((await this.settings.getSettings()).capture_format, job.capture_id);
        const captureDir = await this.ensureCaptureExists(capture_basename);
        this.logger.debug(`writing capture dir: ${captureDir}`);
        const capture = {
            capture_id: job.capture_id,
            error: job.error ?? "",
            started_at: new Date(),
            additional_data: additional_data ?? {},
            capture_name: job.request.job_name === "" ? capture_basename : job.request.job_name
        };
        await this.fs.writeFile(path.join(captureDir, "capture.json"), JSON.stringify(capture), 'utf-8');
        await this.fs.writeFile(path.join(captureDir, "request.json"), JSON.stringify(job.request), 'utf-8');
        return capture_basename;
    }
    /** @inheritdoc */
    async getCapture(capture_basename) {
        const capture_manifest_path = path.join(await this.settings.getCapturesDirectory(), capture_basename, "capture.json");
        const capture_manifest_raw = JSON.parse(await this.fs.readFile(capture_manifest_path, "utf-8"));
        const capture_manifest = schemas_1.CaptureSchema.parse(capture_manifest_raw);
        return capture_manifest;
    }
    /** @inheritdoc */
    async getRequest(capture_basename) {
        const request_path = path.join(await this.settings.getCapturesDirectory(), capture_basename, "request.json");
        const request_raw = JSON.parse(await this.fs.readFile(request_path, "utf-8"));
        const request = schemas_1.JobRequestSchema.parse(request_raw);
        return request;
    }
    /** @inheritdoc */
    async patchCapture(capture_basename, new_name) {
        const captures_directory = await this.settings.getCapturesDirectory();
        const new_path = path.join(captures_directory, new_name);
        await this.fs.rename(path.join(captures_directory, capture_basename), path.join(captures_directory, new_name));
        const manifest_path = path.join(new_path, "capture.json");
        const manifest_raw = JSON.parse(await this.fs.readFile(manifest_path, "utf-8"));
        const manifest = schemas_1.CaptureSchema.parse(manifest_raw);
        manifest.capture_name = new_name;
        await this.fs.writeFile(manifest_path, JSON.stringify(manifest), 'utf-8');
    }
    /**
     * Abstracts the logic needed to list subfolders with a given file in a folder. Returns none if the folder does not exist.
     * @param directory The directory to search in.
     * @param filename The filename that must be present in the found subfolder in order for it to be included.
     * @param create_if_missing Create the root folder 'directory' if it does not exist and return early.
     * @returns An array of paths which match the criteria.
     */
    async listSubfoldersWithFile(directory, filename, create_if_missing) {
        try {
            await this.fs.access(directory);
        }
        catch {
            if (create_if_missing) {
                this.logger.warn(`${directory} does not exist, creating...`);
                await this.fs.mkdir(directory, { recursive: true });
            }
            return [];
        }
        const entries = await this.fs.readdir(directory, { withFileTypes: true });
        const files = [];
        for (const entry of entries) {
            if (entry.isDirectory()) {
                try {
                    await this.fs.access(path.join(directory, entry.name, filename));
                    files.push(entry.name);
                }
                catch {
                    this.logger.error(`could not open manifest.json for: ${entry.name}`);
                }
            }
        }
        return files;
    }
    /** @inheritdoc */
    async listPluginBasenames() {
        const pluginsPath = (await this.settings.getSettings()).plugins_directory;
        return this.listSubfoldersWithFile(pluginsPath, 'manifest.json', true);
    }
    /** @inheritdoc */
    async listCaptureBasenames() {
        const capturesPath = await this.settings.getCapturesDirectory();
        return this.listSubfoldersWithFile(capturesPath, 'capture.json', true);
    }
    /** @inheritdoc */
    async listEventBasenames(capture_basename) {
        const events_path = path.join(await this.settings.getCapturesDirectory(), capture_basename);
        return this.listSubfoldersWithFile(events_path, 'event.json', true);
    }
    /** @inheritdoc */
    async getSettingsFile() {
        try {
            const content = await this.fs.readFile(this.settings_path, "utf-8");
            const defaults = this.settings.getDefaultSettings();
            const parsed = (0, schemas_1.AppSettingsSchemaFactory)(defaults.plugins_directory, defaults.captures_directory).safeParse(JSON.parse(content));
            if (parsed.data) {
                return parsed.data;
            }
            if (parsed.error) {
                // Invalid format
                console.error("Failed to load settings. File may be corrupted.");
                const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
                const corruptedPath = new String(this.settings_path).replace(/\\.json$/, `_${timestamp}_corrupted.json`);
                await this.fs.rename(this.settings_path, corruptedPath);
                const blankSettings = this.settings.getDefaultSettings();
                await this.fs.writeFile(this.settings_path, JSON.stringify(blankSettings, null, 2), 'utf-8');
                return blankSettings;
            }
        }
        catch (raw_error) {
            const error = raw_error;
            if (error.code === "ENOENT") {
                this.logger.info(`Creating default settings at ${this.data_directory}`);
                // Settings file doesn't exist – create a default one
                const blankSettings = this.settings.getDefaultSettings();
                //Initialize the data directory
                await this.fs.mkdir(this.data_directory, { recursive: true });
                //Initialize the settings file
                await this.fs.writeFile(this.settings_path, JSON.stringify(blankSettings, null, 2), 'utf-8');
                //Initialize the Plugins folder
                await this.fs.mkdir(blankSettings.plugins_directory, { recursive: true });
                //Initialize the Captures folder
                await this.fs.mkdir(blankSettings.captures_directory, { recursive: true });
                return blankSettings;
            }
            console.error(`Failed to load settings. Unknown error. Will load default settings instead. ${raw_error.message} \n${raw_error.stack}`);
        }
        const blankSettings = this.settings.getDefaultSettings();
        return blankSettings;
    }
    /** @inheritdoc */
    async writeSettingsFile(settings) {
        await this.fs.mkdir(this.data_directory, { recursive: true });
        await this.fs.writeFile(this.settings_path, JSON.stringify(settings, null, 2), "utf-8");
    }
}
exports.default = FileSystemService;
//# sourceMappingURL=filesystem_service.js.map