/**
 * src/tenants/workspace.ts
 * مدير ملفات مساحة العمل — قراءة وكتابة ملفات المستأجر
 *
 * Each tenant gets a workspace under <workspacesRoot>/<tenantId>/. The root
 * defaults to the platform-native data dir (~/Library/Application Support/
 * Muassis/workspaces on macOS) so workspaces stay alongside the SQLite DB
 * and survive package upgrades. Override via MUASSIS_WORKSPACE_ROOT.
 */
import { mkdir, readFile, writeFile, readdir, stat, copyFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createChildLogger } from '../utils/logger.js';
import { getWorkspacesRoot } from '../utils/data-dir.js';
const log = createChildLogger('tenants:workspace');
/** Locate the workspace-template/ directory shipped with the npm package. */
function getTemplateDir() {
    // dist/tenants/workspace.js — three levels up reaches the package root.
    const here = dirname(fileURLToPath(import.meta.url));
    const candidates = [
        join(here, '..', '..', 'workspace-template'),
        join(here, '..', '..', '..', 'workspace-template'),
    ];
    for (const candidate of candidates) {
        if (existsSync(candidate))
            return candidate;
    }
    return candidates[0];
}
// ─── Core Operations ─────────────────────────────────────────────────
/** الحصول على المسار الكامل لملف */
function getFilePath(tenantId, filename) {
    // (Round-7 fix) Strict path-traversal defense. The prior single-pass
    // `replace(/\.\./g, '')` is bypassable with ".../" (two replacements
    // collapse into "..") and only blocks naive cases. New approach:
    //   1. Forbid absolute paths (filename starts with / or contains :).
    //   2. Strip leading slashes.
    //   3. Resolve the joined path, then assert it stays inside the
    //      tenant's workspace dir. Any escape throws.
    if (filename.includes('\0'))
        throw new Error('invalid filename: null byte');
    if (/^[A-Za-z]:[\\/]/.test(filename))
        throw new Error('invalid filename: absolute Windows path');
    const cleaned = filename.replace(/^[\\/]+/, '');
    const tenantRoot = join(getWorkspacesRoot(), tenantId);
    const resolved = join(tenantRoot, cleaned);
    // Normalize separators on Windows; ensure resolved path is under tenantRoot.
    const normRoot = tenantRoot.endsWith('/') ? tenantRoot : tenantRoot + '/';
    if (resolved !== tenantRoot && !resolved.startsWith(normRoot) && !resolved.startsWith(tenantRoot + '\\')) {
        throw new Error(`path traversal blocked: ${filename}`);
    }
    return resolved;
}
/** Absolute path to a tenant's workspace root. */
export function getTenantWorkspacePath(tenantId) {
    return join(getWorkspacesRoot(), tenantId);
}
/** التأكد من وجود مجلد */
export async function ensureDirectory(tenantId, subdir) {
    const dirPath = subdir
        ? join(getWorkspacesRoot(), tenantId, subdir)
        : join(getWorkspacesRoot(), tenantId);
    await mkdir(dirPath, { recursive: true });
}
/**
 * قراءة ملف من مساحة العمل
 * @returns محتوى الملف أو null إذا لم يكن موجوداً
 */
export async function readWorkspaceFile(tenantId, filename) {
    const filePath = getFilePath(tenantId, filename);
    try {
        const content = await readFile(filePath, 'utf-8');
        return content;
    }
    catch (err) {
        if (err.code === 'ENOENT') {
            return null;
        }
        log.error({ tenantId, filename, error: err.message }, 'Failed to read workspace file');
        throw err;
    }
}
/**
 * كتابة ملف في مساحة العمل
 * ينشئ المجلدات تلقائياً
 */
export async function writeWorkspaceFile(tenantId, filename, content) {
    const filePath = getFilePath(tenantId, filename);
    await mkdir(dirname(filePath), { recursive: true });
    await writeFile(filePath, content, 'utf-8');
    log.debug({ tenantId, filename, bytes: content.length }, 'Workspace file written');
}
/**
 * سرد ملفات مساحة العمل
 */
export async function listWorkspaceFiles(tenantId, directory) {
    const dirPath = directory
        ? join(getWorkspacesRoot(), tenantId, directory)
        : join(getWorkspacesRoot(), tenantId);
    try {
        const entries = await readdir(dirPath, { withFileTypes: true });
        const files = [];
        for (const entry of entries) {
            const prefix = directory ? `${directory}/` : '';
            if (entry.isFile()) {
                files.push(`${prefix}${entry.name}`);
            }
            else if (entry.isDirectory()) {
                files.push(`${prefix}${entry.name}/`);
            }
        }
        return files;
    }
    catch (err) {
        if (err.code === 'ENOENT') {
            return [];
        }
        throw err;
    }
}
// ─── Workspace Provisioning ──────────────────────────────────────────
/**
 * Recursively copy `src` into `dest`, applying the placeholder substitutions
 * to .md files as we go. Existing files at `dest` are skipped so re-running
 * provisionWorkspace on an established tenant is safe.
 */
async function copyTemplateTree(src, dest, substitutions) {
    let copied = 0;
    const entries = await readdir(src, { withFileTypes: true });
    for (const entry of entries) {
        const srcPath = join(src, entry.name);
        const destPath = join(dest, entry.name);
        if (entry.isDirectory()) {
            await mkdir(destPath, { recursive: true });
            copied += await copyTemplateTree(srcPath, destPath, substitutions);
            continue;
        }
        if (existsSync(destPath))
            continue;
        if (entry.name.endsWith('.md') || entry.name.endsWith('.txt')) {
            let content = await readFile(srcPath, 'utf-8');
            for (const [token, value] of Object.entries(substitutions)) {
                content = content.split(token).join(value);
            }
            await writeFile(destPath, content, 'utf-8');
        }
        else {
            await copyFile(srcPath, destPath);
        }
        copied++;
    }
    return copied;
}
/**
 * تجهيز مساحة عمل جديدة لمستأجر — إنشاء الهيكل والملفات الأساسية
 *
 * Source of truth is `workspace-template/` shipped with the package: 5 top
 * docs (SOUL, BUSINESS, PRODUCTS, PROCESSES, TEAM), 5 dirs (ANALYTICS,
 * CUSTOMERS, KNOWLEDGE, MEMORY, SKILLS), and 12 starter SKILLS/*.md files.
 * Placeholders in those templates ([اسم الشركة], [اسم الوكيل]) get replaced
 * with the tenant's real values.
 *
 * If the template directory is missing (unusual — only happens in dev runs
 * before `npm run build`), we fall back to inline minimal templates.
 */
export async function provisionWorkspace(tenantId, businessName, agentName) {
    log.info({ tenantId, businessName, agentName }, 'Provisioning new workspace');
    const tenantDir = getTenantWorkspacePath(tenantId);
    await mkdir(tenantDir, { recursive: true });
    const templateDir = getTemplateDir();
    const substitutions = {
        '[اسم الشركة]': businessName,
        '[اسم الوكيل]': agentName,
        '[BUSINESS_NAME]': businessName,
        '[AGENT_NAME]': agentName,
    };
    let templateFilesCopied = 0;
    if (existsSync(templateDir)) {
        try {
            templateFilesCopied = await copyTemplateTree(templateDir, tenantDir, substitutions);
        }
        catch (err) {
            log.warn({ tenantId, templateDir, error: err.message }, 'Template copy failed — falling back to inline templates');
        }
    }
    else {
        log.warn({ tenantId, templateDir }, 'workspace-template/ not found — using inline templates');
    }
    // Fallback: ensure the core docs/dirs exist even if the template tree was
    // missing or partial. Inline templates only fill gaps — they don't overwrite.
    for (const dir of ['ANALYTICS', 'CUSTOMERS', 'KNOWLEDGE', 'MEMORY', 'SKILLS', 'LOGS']) {
        await ensureDirectory(tenantId, dir);
    }
    await writeIfMissing(tenantId, 'SOUL.md', () => generateSoulTemplate(agentName, businessName));
    await writeIfMissing(tenantId, 'BUSINESS.md', () => generateBusinessTemplate(businessName));
    await writeIfMissing(tenantId, 'PRODUCTS.md', generateProductsTemplate);
    await writeIfMissing(tenantId, 'PROCESSES.md', generateProcessesTemplate);
    await writeIfMissing(tenantId, 'TEAM.md', generateTeamTemplate);
    log.info({ tenantId, templateFilesCopied, path: tenantDir }, 'Workspace provisioned successfully');
}
async function writeIfMissing(tenantId, filename, produce) {
    const existing = await readWorkspaceFile(tenantId, filename);
    if (existing !== null)
        return;
    await writeWorkspaceFile(tenantId, filename, produce());
}
// ─── Templates (fallback only) ───────────────────────────────────────
function generateSoulTemplate(agentName, businessName) {
    return `# ${agentName} — روح الوكيل

أنا **${agentName}**، موظف الذكاء الاصطناعي في **${businessName}**.

## شخصيتي
- ودود ومهني
- أتحدث بالعربية والإنجليزية حسب لغة العميل
- أساعد العملاء بسرعة وكفاءة

## أسلوبي
- ردود مختصرة ومفيدة
- أستخدم إيموجي باعتدال 😊
- أحترم وقت العميل

## حدودي
- لا أخترع معلومات
- أحول للفريق البشري عند الحاجة
- لا أشارك معلومات سرية`;
}
function generateBusinessTemplate(businessName) {
    return `# ${businessName} — معلومات العمل

## عن الشركة
اسم الشركة: ${businessName}
<!-- أضف وصف الشركة هنا -->

## ساعات العمل
<!-- مثال: الأحد - الخميس، 9 صباحاً - 5 مساءً -->

## الموقع
<!-- أضف العنوان هنا -->

## التواصل
<!-- أرقام هواتف، إيميل، حسابات تواصل اجتماعي -->`;
}
function generateProductsTemplate() {
    return `# المنتجات والخدمات

<!-- أضف منتجاتك وخدماتك هنا -->

## مثال:
### اسم المنتج
- **الوصف:** وصف مختصر
- **السعر:** XX ريال
- **متوفر:** نعم/لا`;
}
function generateProcessesTemplate() {
    return `# العمليات والإجراءات

## كيف نتعامل مع الحجوزات
<!-- خطوات الحجز -->

## كيف نتعامل مع الشكاوى
<!-- خطوات معالجة الشكاوى -->

## كيف نحول لموظف بشري
عندما يطلب العميل التحدث مع شخص حقيقي:
1. اعتذر بلطف
2. أخبره أنك ستحوله للفريق
3. اجمع معلومات التواصل إذا لزم الأمر`;
}
function generateTeamTemplate() {
    return `# الفريق

<!-- أضف أعضاء الفريق الذين يمكن التحويل لهم -->

## مثال:
### أحمد — مدير المبيعات
- **المسؤولية:** استفسارات المبيعات والأسعار
- **التواصل:** سيتم التحويل تلقائياً`;
}
// Helper subdir/stat utilities used by other modules ────────────────────
/** Stat a workspace path (returns null on ENOENT). */
export async function statWorkspaceFile(tenantId, filename) {
    try {
        const s = await stat(getFilePath(tenantId, filename));
        return { size: s.size, isDirectory: s.isDirectory() };
    }
    catch (err) {
        if (err.code === 'ENOENT')
            return null;
        throw err;
    }
}
//# sourceMappingURL=workspace.js.map