/**
 * src/onboarding/custom-skills.ts
 * LLM-generated starter skills for the 'other' (custom) sector.
 *
 * When a tenant picks "Other / آخر" during onboarding, they describe
 * their business in free text. This module asks the brain-tier model to
 * generate 3-6 sector-specific skill markdown files, validates each, and
 * uploads to the tenant's Supabase Storage SKILLS/ folder where the runtime
 * skill loader reads from.
 *
 * It does NOT generate the universal skills (greeting / complaint / escalate /
 * follow-up / answer-inquiry / zatca-invoice / daily-report / qualify-lead) —
 * those load from workspace-template/SKILLS/ for every tenant via the
 * filesystem-fallback in src/agent/skills.ts.
 */
import { createChildLogger } from '../utils/logger.js';
import { supabase } from '../utils/supabase.js';
import { callWithFailover } from '../models/failover.js';
import { writeWorkspaceFile } from '../tenants/workspace.js';
const log = createChildLogger('onboarding:custom-skills');
const REQUIRED_SECTIONS = ['متى تستخدم', 'التعليمات', 'أمثلة'];
// Universal skills that already exist for every tenant — the LLM must NOT
// regenerate these.
const UNIVERSAL_SKILL_NAMES = [
    'greeting / الترحيب',
    'handle-complaint / معالجة الشكاوى',
    'escalate-to-human / التصعيد للموظف البشري',
    'follow-up / المتابعة',
    'answer-inquiry / الرد على الاستفسارات',
    'zatca-invoice / فاتورة ZATCA',
    'daily-report / التقرير اليومي',
    'qualify-lead / تأهيل العميل',
];
// ─── Prompt ──────────────────────────────────────────────────────────
function buildSystemPrompt() {
    return `أنت خبير في تصميم مهارات (skills) لوكيل ذكاء اصطناعي خدمة عملاء يعمل على واتساب لشركة سعودية. الشركة عميلتك وصفت عملها بالنص الحر وأنت لازم تولّد لها مهارات مخصصة لقطاعها.

القواعد:

1. لا تولّد المهارات العالمية المتوفرة مسبقاً (سيتم رفضها):
${UNIVERSAL_SKILL_NAMES.map(s => `   - ${s}`).join('\n')}

2. ولّد 3 إلى 6 مهارات **مخصصة لهذا القطاع تحديداً** — لا مهارات عامة.

3. كل مهارة **ملف ماركداون** بهالشكل بالضبط:

\`\`\`markdown
---
name: <اسم المهارة بالعربية>
priority: <رقم من 1 إلى 5؛ 1 = الأعلى أولوية>
keywords: <كلمات مفتاحية مفصولة بفواصل، تشمل لهجة سعودية إن أمكن>
---

# <اسم المهارة>

## متى تستخدم
<متى يتم تفعيل هالمهارة — جملتين أو ثلاث>

## التعليمات
1. <خطوة>
2. <خطوة>
3. <خطوة>
... (5-8 خطوات تفصيلية)

### قالب الرد
"<نموذج رد متوقع للعميل، باللهجة الحجازية أو فصحى مبسطة>"

### حالات التصعيد للموظف البشري
- <حالة 1>
- <حالة 2>

## أمثلة
- <رسالة من عميل>
- <رسالة من عميل>
- <رسالة من عميل>
- <رسالة من عميل>
\`\`\`

4. **اللهجة:** حجازية/سعودية ودية، مو فصحى جامدة. استخدم مصطلحات سعودية واقعية (مدى، تمويل، إيجار، قسط، STC Pay، إلخ) إذا تناسب القطاع.

5. **التركيز:** ايش هي المشكلات الفريدة اللي تواجه هالعمل بالذات؟ ولّد مهارات لها.

أرجع الناتج كـ JSON فقط بهالشكل:
\`\`\`json
{
  "skills": [
    {
      "id": "kebab-case-id",
      "filename": "kebab-case-id.md",
      "content": "<full markdown including frontmatter>"
    }
  ]
}
\`\`\`

لا شرح، لا مقدمة، لا خاتمة. JSON فقط.`;
}
function buildUserPrompt(input) {
    const lines = [`وصف العمل:\n${input.businessDescription.trim()}`];
    if (input.agentName)
        lines.push(`اسم الموظف الذكي: ${input.agentName}`);
    if (input.agentRole)
        lines.push(`دور الموظف: ${input.agentRole}`);
    if (input.agentTone)
        lines.push(`الأسلوب المطلوب: ${input.agentTone}`);
    return lines.join('\n\n');
}
// ─── Validation ──────────────────────────────────────────────────────
function validateSkill(skill, idx) {
    if (typeof skill !== 'object' || skill === null) {
        throw new Error(`Skill ${idx} is not an object`);
    }
    const s = skill;
    const id = String(s.id || '').trim();
    const filename = String(s.filename || '').trim();
    const content = String(s.content || '').trim();
    if (!id || !/^[a-z0-9-]+$/.test(id)) {
        throw new Error(`Skill ${idx} has invalid id (must be kebab-case): "${id}"`);
    }
    if (!filename.endsWith('.md')) {
        throw new Error(`Skill ${idx} filename must end with .md: "${filename}"`);
    }
    if (content.length < 200) {
        throw new Error(`Skill ${idx} content too short (${content.length} chars, need ≥200)`);
    }
    if (!content.startsWith('---')) {
        throw new Error(`Skill ${idx} content missing frontmatter`);
    }
    for (const section of REQUIRED_SECTIONS) {
        if (!content.includes(section)) {
            throw new Error(`Skill ${idx} missing required section: ${section}`);
        }
    }
    // Reject if it looks like a universal skill the LLM regenerated.
    const lower = content.toLowerCase();
    if (/^---\s*\n\s*name:\s*الترحيب\b/i.test(content) ||
        /^---\s*\n\s*name:\s*المتابعة\b/i.test(content) ||
        /^---\s*\n\s*name:\s*التقرير اليومي\b/i.test(content) ||
        lower.includes('skill: greeting') && content.length < 500) {
        throw new Error(`Skill ${idx} duplicates a universal skill (${id}) — drop it`);
    }
    return { id, filename, content };
}
function parseSkillsResponse(raw) {
    // Strip code fences if model wrapped JSON in ```json ... ```
    let cleaned = raw.trim();
    const fenceMatch = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/);
    if (fenceMatch)
        cleaned = fenceMatch[1].trim();
    let parsed;
    try {
        parsed = JSON.parse(cleaned);
    }
    catch (err) {
        throw new Error(`Model did not return valid JSON: ${err instanceof Error ? err.message : err}`);
    }
    if (typeof parsed !== 'object' || parsed === null) {
        throw new Error('Top-level response is not an object');
    }
    const skills = parsed.skills;
    if (!Array.isArray(skills) || skills.length === 0) {
        throw new Error('Response.skills is missing or empty');
    }
    if (skills.length > 8) {
        throw new Error(`Response.skills has too many entries (${skills.length}, max 8)`);
    }
    return skills.map(validateSkill);
}
// ─── Storage ─────────────────────────────────────────────────────────
async function uploadSkillToStorage(tenantId, filename, content) {
    const bucket = `tenant-${tenantId}`;
    const path = `SKILLS/${filename}`;
    const blob = new Blob([content], { type: 'text/markdown' });
    const { error } = await supabase.storage
        .from(bucket)
        .upload(path, blob, { upsert: true, contentType: 'text/markdown' });
    if (error) {
        // Bucket may not exist yet — try to create it then re-upload once.
        if (/not found|does not exist|bucket/i.test(error.message)) {
            log.info({ tenantId, bucket }, 'Tenant bucket missing — creating');
            const { error: createErr } = await supabase.storage.createBucket(bucket, { public: false });
            if (createErr && !/already exists/i.test(createErr.message)) {
                throw new Error(`Failed to create bucket ${bucket}: ${createErr.message}`);
            }
            const retry = await supabase.storage
                .from(bucket)
                .upload(path, blob, { upsert: true, contentType: 'text/markdown' });
            if (retry.error) {
                throw new Error(`Failed to upload skill after bucket create: ${retry.error.message}`);
            }
            return;
        }
        throw new Error(`Failed to upload skill ${filename}: ${error.message}`);
    }
}
// ─── Public ──────────────────────────────────────────────────────────
const MAX_ATTEMPTS = 2;
export async function generateCustomSkills(input) {
    const description = input.businessDescription?.trim() || '';
    if (description.length < 30) {
        throw new Error('Business description too short — provide at least 30 characters');
    }
    if (description.length > 4000) {
        throw new Error('Business description too long — max 4000 characters');
    }
    const messages = [
        { role: 'system', content: buildSystemPrompt() },
        { role: 'user', content: buildUserPrompt(input) },
    ];
    const options = {
        temperature: 0.6,
        maxTokens: 4096,
        responseFormat: { type: 'json_object' },
        timeoutMs: 60_000, // larger than agent calls — generation is one-off
    };
    let lastError = null;
    for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
        try {
            const result = await callWithFailover('brain', messages, options);
            const skills = parseSkillsResponse(result.content || '');
            log.info({ tenantId: input.tenantId, attempt, count: skills.length }, 'Custom skills generated');
            return skills;
        }
        catch (err) {
            lastError = err instanceof Error ? err : new Error(String(err));
            log.warn({ tenantId: input.tenantId, attempt, error: lastError.message }, 'Skill generation attempt failed');
            // Add a corrective hint for the retry
            if (attempt < MAX_ATTEMPTS) {
                messages.push({
                    role: 'user',
                    content: `المحاولة السابقة فشلت بسبب: ${lastError.message}\nحاول مرة أخرى وأرجع JSON صالح فقط بدون أي شرح.`,
                });
            }
        }
    }
    throw new Error(`Custom skill generation failed after ${MAX_ATTEMPTS} attempts: ${lastError?.message}`);
}
/**
 * Generate + persist starter skills for a custom (Other) sector tenant.
 * Writes to BOTH filesystem (for inspection) and Supabase Storage (where
 * the runtime skill loader reads). Returns the list of generated skills.
 *
 * Failures uploading individual skills are logged and skipped — the
 * function returns whatever did succeed. Total failure throws.
 */
export async function generateAndPersistCustomSkills(input) {
    const skills = await generateCustomSkills(input);
    // Persist in parallel — Storage upload + filesystem write per skill
    const results = await Promise.allSettled(skills.map(async (skill) => {
        // Filesystem (for human inspection of the tenant's workspace)
        await writeWorkspaceFile(input.tenantId, `SKILLS/${skill.filename}`, skill.content);
        // Supabase Storage (where the runtime loader reads from)
        await uploadSkillToStorage(input.tenantId, skill.filename, skill.content);
    }));
    const failed = results.filter(r => r.status === 'rejected');
    if (failed.length > 0) {
        log.warn({
            tenantId: input.tenantId,
            failed: failed.length,
            total: skills.length,
            firstError: failed[0].status === 'rejected' ? String(failed[0].reason) : '',
        }, 'Some custom skills failed to persist');
    }
    if (failed.length === skills.length) {
        throw new Error('All custom skills failed to persist — check Supabase Storage permissions');
    }
    log.info({ tenantId: input.tenantId, persisted: skills.length - failed.length, total: skills.length }, 'Custom skills persisted');
    return skills;
}
//# sourceMappingURL=custom-skills.js.map