import fs from "node:fs/promises";
import path from "node:path";
import { getLogger } from "../../util/logger.js";
import { factNameToFileName, parseSkillFile, serializeSkillFile, } from "./parser.js";
/** Relative path from the app root where app skill facts are stored */
const APP_SKILLS_RELATIVE_DIR = "skills/app";
/** Virtual path prefix shown to the user/UI */
const VIRTUAL_PATH_PREFIX = ".superblocks/skills/app";
/**
 * Manages reading and writing app skill fact files on disk.
 *
 * Facts are stored as markdown files with YAML frontmatter in
 * `<appRootDirPath>/skills/app/*.md`.
 */
export class AppSkillsManager {
    appRootDirPath;
    constructor(appRootDirPath) {
        this.appRootDirPath = appRootDirPath;
    }
    get skillsDir() {
        return path.join(this.appRootDirPath, APP_SKILLS_RELATIVE_DIR);
    }
    toVirtualPath(absolutePath) {
        const fileName = path.basename(absolutePath);
        return `${VIRTUAL_PATH_PREFIX}/${fileName}`;
    }
    fromVirtualPath(virtualPath) {
        const fileName = path.basename(virtualPath);
        return path.join(this.skillsDir, fileName);
    }
    /**
     * Load all facts from disk. Returns an empty array if the directory doesn't exist.
     */
    async loadFacts() {
        let entries;
        try {
            entries = await fs.readdir(this.skillsDir);
        }
        catch (err) {
            if (err.code === "ENOENT") {
                return [];
            }
            throw err;
        }
        const facts = [];
        for (const entry of entries) {
            if (!entry.endsWith(".md"))
                continue;
            const absolutePath = path.join(this.skillsDir, entry);
            try {
                const raw = await fs.readFile(absolutePath, "utf-8");
                const parsed = parseSkillFile(raw);
                if (parsed) {
                    facts.push({ ...parsed, path: this.toVirtualPath(absolutePath) });
                }
            }
            catch (error) {
                getLogger().warn({ absolutePath, error }, "Failed to read or parse skill file");
            }
        }
        // Sort by createdAt ascending (oldest first)
        facts.sort((a, b) => {
            if (a.createdAt && b.createdAt) {
                return a.createdAt.localeCompare(b.createdAt);
            }
            return 0;
        });
        return facts;
    }
    /**
     * Convert loaded AppSkillFact array to AiAppFact array for the UI.
     */
    static toAiAppFacts(facts) {
        return facts.map((f) => ({
            path: f.path,
            name: f.name,
            description: f.description,
            content: f.content,
            accessType: f.accessType,
            isEnabled: f.isEnabled,
            createdAt: f.createdAt,
        }));
    }
    /**
     * Find a fact by display name (case-insensitive).
     */
    async findFactByName(name) {
        const normalizedName = name.trim().toLowerCase();
        if (!normalizedName) {
            return undefined;
        }
        const facts = await this.loadFacts();
        return facts.find((fact) => fact.name.trim().toLowerCase() === normalizedName);
    }
    /**
     * Save a new fact to disk. If a fact with the same name already exists, it will be overwritten.
     */
    async saveFact(fact) {
        await fs.mkdir(this.skillsDir, { recursive: true });
        const createdAt = new Date().toISOString();
        const fileName = `${factNameToFileName(fact.name)}.md`;
        const absolutePath = path.join(this.skillsDir, fileName);
        const parsed = {
            name: fact.name,
            description: fact.description,
            content: fact.content,
            accessType: "on_demand",
            isEnabled: fact.isEnabled !== false,
            createdAt,
        };
        await fs.writeFile(absolutePath, serializeSkillFile(parsed), "utf-8");
        return {
            ...parsed,
            path: this.toVirtualPath(absolutePath),
        };
    }
    /**
     * Update an existing fact by its virtual path.
     */
    async updateFact(virtualPath, updates) {
        const absolutePath = this.fromVirtualPath(virtualPath);
        const raw = await fs.readFile(absolutePath, "utf-8");
        const parsed = parseSkillFile(raw);
        if (!parsed) {
            throw new Error(`Could not parse fact file at ${virtualPath}`);
        }
        const updated = {
            ...parsed,
            name: updates.name,
            description: updates.description,
            content: updates.content,
        };
        await fs.writeFile(absolutePath, serializeSkillFile(updated), "utf-8");
        return {
            ...updated,
            path: virtualPath,
        };
    }
    /**
     * Delete a fact file by its virtual path.
     */
    async deleteFact(virtualPath) {
        const absolutePath = this.fromVirtualPath(virtualPath);
        try {
            await fs.unlink(absolutePath);
        }
        catch (err) {
            if (err.code === "ENOENT") {
                // Already gone — not an error
                return;
            }
            throw err;
        }
    }
    /**
     * Toggle the isEnabled field of a fact by its virtual path.
     * Returns the updated AiAppFact.
     */
    async toggleFact(virtualPath, isEnabled) {
        const absolutePath = this.fromVirtualPath(virtualPath);
        const raw = await fs.readFile(absolutePath, "utf-8");
        const parsed = parseSkillFile(raw);
        if (!parsed) {
            throw new Error(`Could not parse fact file at ${virtualPath}`);
        }
        const updated = { ...parsed, isEnabled };
        await fs.writeFile(absolutePath, serializeSkillFile(updated), "utf-8");
        return {
            ...updated,
            path: virtualPath,
        };
    }
    /**
     * Load facts and format them for injection into the system prompt.
     * Only includes facts where isEnabled=true AND accessType="always".
     * Facts with accessType="on_demand" are available but not auto-injected.
     */
    async loadFactsForPrompt() {
        const facts = await this.loadFacts();
        const alwaysOn = facts.filter((f) => f.isEnabled && f.accessType === "always");
        if (alwaysOn.length === 0) {
            return "";
        }
        const items = alwaysOn
            .map((f) => `### ${f.name}
${f.description ? `${f.description}\n\n` : ""}${f.content}`)
            .join("\n\n");
        return `<app_knowledge>
## App-Specific Knowledge

The following facts were explicitly saved by the user for this application. They are MANDATORY on every turn and override general defaults.

**You MUST:**
- Apply every enabled app fact regardless of what the task is
- Treat them as authoritative user-defined requirements, not optional hints
- If a fact specifies a pattern, style, or constraint: follow it without exception
- These take precedence over your general defaults, but never over safety guidelines

${items}
</app_knowledge>`;
    }
}
//# sourceMappingURL=manager.js.map