"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 });
exports.parseNoteFile = parseNoteFile;
exports.parseTaskFile = parseTaskFile;
exports.parseSkillFile = parseSkillFile;
exports.parseNoteDir = parseNoteDir;
exports.parseTaskDir = parseTaskDir;
exports.parseSkillDir = parseSkillDir;
exports.diffRelations = diffRelations;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const frontmatter_1 = require("./frontmatter");
const attachment_types_1 = require("../graphs/attachment-types");
const events_log_1 = require("./events-log");
const logger_1 = require("../lib/logger");
const log = (0, logger_1.createLogger)('file-import');
const VALID_STATUSES = ['backlog', 'todo', 'in_progress', 'review', 'done', 'cancelled'];
const VALID_PRIORITIES = ['critical', 'high', 'medium', 'low'];
const VALID_SOURCES = ['user', 'learned'];
function isoToMs(value) {
    if (value == null)
        return null;
    const d = new Date(value);
    return isNaN(d.getTime()) ? null : d.getTime();
}
function parseTags(raw) {
    if (Array.isArray(raw))
        return raw.filter(t => typeof t === 'string');
    return [];
}
function parseRelations(raw) {
    if (!Array.isArray(raw))
        return [];
    return raw
        .filter(r => r && typeof r === 'object' && typeof r.to === 'string' && typeof r.kind === 'string')
        .map(r => {
        const entry = { to: r.to, kind: r.kind };
        if (typeof r.graph === 'string')
            entry.graph = r.graph;
        return entry;
    });
}
function parseAuthorString(raw) {
    if (typeof raw === 'string' && raw.trim())
        return raw.trim();
    return null;
}
function extractTitleAndContent(body) {
    const lines = body.split('\n');
    const headingMatch = lines[0]?.match(/^#\s+(.+)/);
    if (!headingMatch)
        return { title: '', content: body.trim() };
    const title = headingMatch[1].trim();
    // Skip heading line and the blank line after it
    let start = 1;
    if (lines[start] === '')
        start++;
    const content = lines.slice(start).join('\n').trim();
    return { title, content };
}
/**
 * Determine the entity ID from a file path.
 * New format: .notes/{id}/note.md or .tasks/{id}/task.md or .skills/{id}/skill.md → id from dirname
 * Legacy format: .notes/{id}.md or .tasks/{id}.md → id from basename
 */
function extractId(filePath) {
    const basename = path.basename(filePath, '.md');
    let id;
    if (basename === 'note' || basename === 'task' || basename === 'skill') {
        id = path.basename(path.dirname(filePath));
    }
    else {
        id = basename;
    }
    if (id === '..' || id === '.' || id.includes('\0'))
        return '';
    return id;
}
function parseNoteFile(filePath) {
    try {
        const raw = fs.readFileSync(filePath, 'utf-8');
        const { frontmatter: fm, body } = (0, frontmatter_1.parseMarkdown)(raw);
        const { title, content } = extractTitleAndContent(body);
        const id = extractId(filePath);
        if (!title && !id)
            return null;
        const attachments = (0, attachment_types_1.scanAttachments)(path.dirname(filePath));
        return {
            id,
            title: title || id,
            content,
            tags: parseTags(fm.tags),
            createdAt: isoToMs(fm.createdAt),
            updatedAt: isoToMs(fm.updatedAt),
            version: typeof fm.version === 'number' ? fm.version : null,
            createdBy: parseAuthorString(fm.createdBy),
            updatedBy: parseAuthorString(fm.updatedBy),
            relations: parseRelations(fm.relations),
            attachments,
        };
    }
    catch (err) {
        log.error({ err, filePath }, 'failed to parse note');
        return null;
    }
}
function parseTaskFile(filePath) {
    try {
        const raw = fs.readFileSync(filePath, 'utf-8');
        const { frontmatter: fm, body } = (0, frontmatter_1.parseMarkdown)(raw);
        const { title, content: description } = extractTitleAndContent(body);
        const id = extractId(filePath);
        if (!title && !id)
            return null;
        const status = VALID_STATUSES.includes(fm.status)
            ? fm.status : 'backlog';
        const priority = VALID_PRIORITIES.includes(fm.priority)
            ? fm.priority : 'medium';
        const attachments = (0, attachment_types_1.scanAttachments)(path.dirname(filePath));
        return {
            id,
            title: title || id,
            description,
            status,
            priority,
            tags: parseTags(fm.tags),
            dueDate: isoToMs(fm.dueDate),
            estimate: typeof fm.estimate === 'number' ? fm.estimate : null,
            completedAt: isoToMs(fm.completedAt),
            assignee: typeof fm.assignee === 'string' ? fm.assignee : null,
            createdAt: isoToMs(fm.createdAt),
            updatedAt: isoToMs(fm.updatedAt),
            version: typeof fm.version === 'number' ? fm.version : null,
            createdBy: parseAuthorString(fm.createdBy),
            updatedBy: parseAuthorString(fm.updatedBy),
            relations: parseRelations(fm.relations),
            attachments,
        };
    }
    catch (err) {
        log.error({ err, filePath }, 'failed to parse task');
        return null;
    }
}
function parseStringArray(raw) {
    if (Array.isArray(raw))
        return raw.filter(t => typeof t === 'string');
    return [];
}
function extractDescriptionAndSteps(body) {
    const lines = body.split('\n');
    const headingMatch = lines[0]?.match(/^#\s+(.+)/);
    if (!headingMatch)
        return { title: '', description: body.trim(), steps: [] };
    const title = headingMatch[1].trim();
    let start = 1;
    if (lines[start] === '')
        start++;
    // Find ## Steps section
    const stepsIdx = lines.findIndex((l, i) => i >= start && /^##\s+Steps/i.test(l));
    if (stepsIdx === -1) {
        return { title, description: lines.slice(start).join('\n').trim(), steps: [] };
    }
    const description = lines.slice(start, stepsIdx).join('\n').trim();
    // Parse numbered list after ## Steps
    const steps = [];
    for (let i = stepsIdx + 1; i < lines.length; i++) {
        const stepMatch = lines[i].match(/^\d+\.\s+(.+)/);
        if (stepMatch) {
            steps.push(stepMatch[1].trim());
        }
        else if (lines[i].trim() === '') {
            continue;
        }
        else if (/^##\s+/.test(lines[i])) {
            break; // another heading section
        }
    }
    return { title, description, steps };
}
function parseSkillFile(filePath) {
    try {
        const raw = fs.readFileSync(filePath, 'utf-8');
        const { frontmatter: fm, body } = (0, frontmatter_1.parseMarkdown)(raw);
        const { title, description, steps } = extractDescriptionAndSteps(body);
        const id = extractId(filePath);
        if (!title && !id)
            return null;
        const source = VALID_SOURCES.includes(fm.source)
            ? fm.source : 'user';
        const confidence = typeof fm.confidence === 'number' ? Math.max(0, Math.min(1, fm.confidence)) : 1;
        const attachments = (0, attachment_types_1.scanAttachments)(path.dirname(filePath));
        return {
            id,
            title: title || id,
            description,
            steps,
            triggers: parseStringArray(fm.triggers),
            inputHints: parseStringArray(fm.inputHints),
            filePatterns: parseStringArray(fm.filePatterns),
            tags: parseTags(fm.tags),
            source,
            confidence,
            usageCount: typeof fm.usageCount === 'number' ? fm.usageCount : null,
            lastUsedAt: isoToMs(fm.lastUsedAt),
            createdAt: isoToMs(fm.createdAt),
            updatedAt: isoToMs(fm.updatedAt),
            version: typeof fm.version === 'number' ? fm.version : null,
            createdBy: parseAuthorString(fm.createdBy),
            updatedBy: parseAuthorString(fm.updatedBy),
            relations: parseRelations(fm.relations),
            attachments,
        };
    }
    catch (err) {
        log.error({ err, filePath }, 'failed to parse skill');
        return null;
    }
}
// ---------------------------------------------------------------------------
// Event-sourced directory parsers (new format)
// ---------------------------------------------------------------------------
/** Parse a note from its entity directory (events.jsonl + content.md). */
function parseNoteDir(dirPath) {
    try {
        const eventsPath = path.join(dirPath, 'events.jsonl');
        if (!fs.existsSync(eventsPath))
            return null;
        const events = (0, events_log_1.readEvents)(eventsPath);
        const contentPath = path.join(dirPath, 'content.md');
        const content = fs.existsSync(contentPath) ? fs.readFileSync(contentPath, 'utf-8') : '';
        const parsed = (0, events_log_1.replayNoteEvents)(events, content);
        if (!parsed)
            return null;
        // Refresh attachments from disk (overrides event-derived list with accurate metadata)
        parsed.attachments = (0, attachment_types_1.scanAttachments)(dirPath);
        return parsed;
    }
    catch (err) {
        log.error({ err, dirPath }, 'failed to parse note dir');
        return null;
    }
}
/** Parse a task from its entity directory (events.jsonl + description.md). */
function parseTaskDir(dirPath) {
    try {
        const eventsPath = path.join(dirPath, 'events.jsonl');
        if (!fs.existsSync(eventsPath))
            return null;
        const events = (0, events_log_1.readEvents)(eventsPath);
        const descPath = path.join(dirPath, 'description.md');
        const description = fs.existsSync(descPath) ? fs.readFileSync(descPath, 'utf-8') : '';
        const parsed = (0, events_log_1.replayTaskEvents)(events, description);
        if (!parsed)
            return null;
        parsed.attachments = (0, attachment_types_1.scanAttachments)(dirPath);
        return parsed;
    }
    catch (err) {
        log.error({ err, dirPath }, 'failed to parse task dir');
        return null;
    }
}
/** Parse a skill from its entity directory (events.jsonl + description.md). */
function parseSkillDir(dirPath) {
    try {
        const eventsPath = path.join(dirPath, 'events.jsonl');
        if (!fs.existsSync(eventsPath))
            return null;
        const events = (0, events_log_1.readEvents)(eventsPath);
        const descPath = path.join(dirPath, 'description.md');
        const description = fs.existsSync(descPath) ? fs.readFileSync(descPath, 'utf-8') : '';
        const parsed = (0, events_log_1.replaySkillEvents)(events, description);
        if (!parsed)
            return null;
        parsed.attachments = (0, attachment_types_1.scanAttachments)(dirPath);
        return parsed;
    }
    catch (err) {
        log.error({ err, dirPath }, 'failed to parse skill dir');
        return null;
    }
}
function relationKey(r) {
    return `${r.to}:${r.kind}:${r.graph ?? ''}`;
}
function diffRelations(current, desired) {
    const currentKeys = new Set(current.map(relationKey));
    const desiredKeys = new Set(desired.map(relationKey));
    const desiredMap = new Map(desired.map(r => [relationKey(r), r]));
    const currentMap = new Map(current.map(r => [relationKey(r), r]));
    const toAdd = [];
    const toRemove = [];
    for (const key of desiredKeys) {
        if (!currentKeys.has(key))
            toAdd.push(desiredMap.get(key));
    }
    for (const key of currentKeys) {
        if (!desiredKeys.has(key))
            toRemove.push(currentMap.get(key));
    }
    return { toAdd, toRemove };
}
