/**
 * Template Service
 *
 * Loads, lists, and applies team templates from config/templates/.
 * Supports both the new TeamTemplate format (with verificationPipeline)
 * and legacy flat JSON format (backward compatible).
 *
 * @module services/template/template
 */
import { readFileSync, readdirSync, existsSync, statSync, mkdirSync, copyFileSync, writeFileSync } from 'fs';
import { homedir } from 'os';
import { join, resolve } from 'path';
import { LoggerService } from '../core/logger.service.js';
import { randomUUID } from 'crypto';
import { isValidTeamTemplate } from '../../types/team-template.types.js';
import { SkillTierService } from '../skill/skill-tier.service.js';
import { CLOUD_CONSTANTS } from '../../constants.js';
// =============================================================================
// Constants
// =============================================================================
const logger = LoggerService.getInstance().createComponentLogger('TemplateService');
/** Default path to templates directory */
const DEFAULT_TEMPLATES_DIR = resolve(process.cwd(), 'config/templates');
// =============================================================================
// Service
// =============================================================================
/**
 * TemplateService manages team templates.
 * Loads templates from config/templates/ directory, provides
 * listing/querying, and creates teams from templates.
 *
 * Singleton pattern matching other Crewly services.
 */
export class TemplateService {
    static instance = null;
    /** Loaded templates indexed by ID */
    templates = new Map();
    /** Templates directory path */
    templatesDir;
    /** Whether templates have been loaded */
    loaded = false;
    constructor(templatesDir) {
        this.templatesDir = templatesDir ?? DEFAULT_TEMPLATES_DIR;
    }
    /**
     * Get the singleton instance.
     *
     * @param templatesDir - Optional override for templates directory
     * @returns The singleton instance
     */
    static getInstance(templatesDir) {
        if (!TemplateService.instance) {
            TemplateService.instance = new TemplateService(templatesDir);
        }
        return TemplateService.instance;
    }
    /**
     * Clear the singleton instance (for testing).
     */
    static clearInstance() {
        TemplateService.instance = null;
    }
    /**
     * Load all templates from the templates directory.
     * Scans for template.json files in subdirectories and
     * legacy flat JSON files at the root level.
     *
     * @returns Number of templates loaded
     */
    loadTemplates() {
        this.templates.clear();
        if (!existsSync(this.templatesDir)) {
            this.loaded = true;
            return 0;
        }
        const entries = readdirSync(this.templatesDir);
        for (const entry of entries) {
            const fullPath = join(this.templatesDir, entry);
            const stat = statSync(fullPath);
            if (stat.isDirectory()) {
                // Check for template.json in subdirectory (new format)
                const templateJsonPath = join(fullPath, 'template.json');
                if (existsSync(templateJsonPath)) {
                    this.loadTemplateFile(templateJsonPath, entry);
                }
            }
            else if (entry.endsWith('.json') && !entry.startsWith('.')) {
                // Legacy flat JSON at root level
                this.loadLegacyTemplate(fullPath, entry);
            }
        }
        this.loaded = true;
        return this.templates.size;
    }
    /**
     * List local templates as summaries.
     *
     * @returns Array of local template summaries
     */
    listTemplates() {
        if (!this.loaded)
            this.loadTemplates();
        return Array.from(this.templates.values()).map(t => ({
            id: t.id,
            name: t.name,
            description: t.description,
            category: t.category,
            hierarchical: t.hierarchical,
            roleCount: t.roles.length,
            version: t.version,
            tags: t.tags,
            icon: t.icon,
            source: 'local',
            requiredTier: t.requiredTier,
        }));
    }
    /**
     * Get a specific template by ID.
     *
     * @param id - Template ID
     * @returns The template, or null if not found
     */
    getTemplate(id) {
        if (!this.loaded)
            this.loadTemplates();
        return this.templates.get(id) ?? null;
    }
    /**
     * Create a team from a template.
     * Generates TeamMember objects with IDs, session names,
     * and hierarchy fields based on the template's role definitions.
     *
     * @param templateId - ID of the template to use
     * @param teamName - Name for the new team
     * @param nameOverrides - Optional map of role to custom name
     * @returns The created team result, or null if template not found
     */
    createTeamFromTemplate(templateId, teamName, nameOverrides, userTier) {
        const template = this.getTemplate(templateId);
        if (!template)
            return null;
        // Check if user's tier is sufficient for this template
        const effectiveTier = userTier || CLOUD_CONSTANTS.TIERS.FREE;
        if (template.requiredTier) {
            const tierService = SkillTierService.getInstance();
            if (!tierService.isTierSufficient(effectiveTier, template.requiredTier)) {
                throw new Error(`Template "${template.name}" requires ${template.requiredTier} plan. ` +
                    `Current plan: ${effectiveTier}. Upgrade at https://crewlyai.com/pricing`);
            }
        }
        const members = [];
        const memberIdsByRole = new Map();
        // Resolve skills with tier-based graceful degradation
        const tierService = SkillTierService.getInstance();
        // First pass: create all members
        for (const role of template.roles) {
            const roleMembers = [];
            // Apply skill tier filtering for this role
            let resolvedSkills = role.defaultSkills;
            if (role.defaultSkills.length > 0) {
                const skillInfos = role.defaultSkills.map(id => ({
                    id,
                    // Skills without requiredTier are free by default
                }));
                const resolution = tierService.resolveSkills(skillInfos, effectiveTier);
                resolvedSkills = resolution.skills.map(s => s.id);
                if (resolution.degraded.length > 0) {
                    logger.info('Skills degraded for role due to tier', {
                        templateId,
                        role: role.role,
                        userTier: effectiveTier,
                        degraded: resolution.degraded.map(d => d.original),
                    });
                }
            }
            for (let i = 0; i < role.count; i++) {
                const memberId = randomUUID();
                const suffix = role.count > 1 ? `${i + 1}` : '';
                const name = nameOverrides?.[role.role]
                    ?? `${role.defaultName}${suffix}`;
                const member = {
                    id: memberId,
                    name,
                    sessionName: '',
                    role: role.role,
                    systemPrompt: role.promptAdditions ?? '',
                    agentStatus: 'inactive',
                    workingStatus: 'idle',
                    runtimeType: role.runtimeOverride ?? template.defaultRuntime,
                    hierarchyLevel: role.hierarchyLevel,
                    canDelegate: role.canDelegate,
                    skillOverrides: resolvedSkills.length > 0 ? resolvedSkills : undefined,
                    excludedRoleSkills: role.excludedSkills,
                    createdAt: new Date().toISOString(),
                    updatedAt: new Date().toISOString(),
                    // Organization Model fields from template role
                    jobTitle: role.jobTitle,
                    jobDescription: role.jobDescription,
                    ownershipScope: role.ownershipScope,
                    responsibilityType: role.responsibilityType,
                    autonomyLevel: role.autonomyLevel,
                    expertId: role.expertId,
                    domainSOP: role.domainSOP,
                    riskPolicy: role.riskPolicy,
                };
                members.push(member);
                roleMembers.push(memberId);
            }
            memberIdsByRole.set(role.role, roleMembers);
        }
        // Second pass: wire up hierarchy (parentMemberId, subordinateIds)
        for (const role of template.roles) {
            const currentIds = memberIdsByRole.get(role.role) ?? [];
            if (role.reportsTo) {
                const parentIds = memberIdsByRole.get(role.reportsTo) ?? [];
                if (parentIds.length > 0) {
                    const parentId = parentIds[0]; // Each child reports to first member of parent role
                    for (const childId of currentIds) {
                        const child = members.find(m => m.id === childId);
                        if (child)
                            child.parentMemberId = parentId;
                    }
                    // Update parent's subordinateIds
                    const parent = members.find(m => m.id === parentId);
                    if (parent) {
                        parent.subordinateIds = [...(parent.subordinateIds ?? []), ...currentIds];
                    }
                }
            }
        }
        // Find leader ID
        const leaderRole = template.roles.find(r => r.canDelegate && r.hierarchyLevel === 1);
        const leaderIds = leaderRole ? memberIdsByRole.get(leaderRole.role) : undefined;
        const leaderId = leaderIds?.[0];
        const team = {
            id: randomUUID(),
            name: teamName,
            description: template.description,
            members,
            projectIds: [],
            hierarchical: template.hierarchical,
            leaderId,
            templateId: template.id,
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
            // Organization Model fields from template
            mission: template.mission,
            ownershipScope: template.ownershipScope,
            serviceContract: template.serviceContract,
        };
        // Copy norms from template to team directory
        this.copyTeamNorms(template.id, team.id);
        return {
            team,
            templateId: template.id,
            memberCount: members.length,
        };
    }
    /**
     * Copy norms files from a template to the team's norms directory.
     *
     * Copies config/templates/{templateId}/norms/*.md to ~/.crewly/teams/{teamId}/norms/.
     * Also copies the norms config from template.json into the team's config.json.
     *
     * @param templateId - Source template ID
     * @param teamId - Target team ID
     */
    copyTeamNorms(templateId, teamId) {
        try {
            const normsSourceDir = join(this.templatesDir, templateId, 'norms');
            if (!existsSync(normsSourceDir))
                return;
            const normsFiles = readdirSync(normsSourceDir).filter(f => f.endsWith('.md'));
            if (normsFiles.length === 0)
                return;
            const crewlyHome = join(homedir(), '.crewly');
            const normsTargetDir = join(crewlyHome, 'teams', teamId, 'norms');
            mkdirSync(normsTargetDir, { recursive: true });
            for (const file of normsFiles) {
                copyFileSync(join(normsSourceDir, file), join(normsTargetDir, file));
            }
            // Copy norms config from template.json into team config
            const template = this.getTemplate(templateId);
            if (template) {
                const teamConfigPath = join(crewlyHome, 'teams', teamId, 'config.json');
                if (existsSync(teamConfigPath)) {
                    try {
                        const config = JSON.parse(readFileSync(teamConfigPath, 'utf-8'));
                        const templateJson = this.loadTemplateJson(templateId);
                        if (templateJson?.norms) {
                            config.norms = templateJson.norms;
                            writeFileSync(teamConfigPath, JSON.stringify(config, null, 2));
                        }
                    }
                    catch {
                        // Best-effort — config update failure doesn't block team creation
                    }
                }
            }
            logger.info('Copied team norms from template', {
                templateId,
                teamId,
                normsCount: normsFiles.length,
                files: normsFiles,
            });
        }
        catch (err) {
            logger.warn('Failed to copy team norms (non-fatal)', {
                templateId,
                teamId,
                error: err instanceof Error ? err.message : String(err),
            });
        }
    }
    /**
     * Load the raw template.json for a template (including norms config).
     *
     * @param templateId - Template ID
     * @returns Parsed template.json or null
     */
    loadTemplateJson(templateId) {
        try {
            const jsonPath = join(this.templatesDir, templateId, 'template.json');
            if (!existsSync(jsonPath))
                return null;
            return JSON.parse(readFileSync(jsonPath, 'utf-8'));
        }
        catch {
            return null;
        }
    }
    /**
     * Get the number of loaded templates.
     *
     * @returns Template count
     */
    getTemplateCount() {
        return this.templates.size;
    }
    // ===========================================================================
    // Private helpers
    // ===========================================================================
    /**
     * Load a new-format template.json file.
     *
     * @param filePath - Absolute path to the template.json file
     * @param dirName - Directory name containing the template
     */
    loadTemplateFile(filePath, dirName) {
        try {
            const content = readFileSync(filePath, 'utf-8');
            const data = JSON.parse(content);
            // Check if it's already the new format (has verificationPipeline)
            if (isValidTeamTemplate(data)) {
                this.templates.set(data.id, data);
                return;
            }
            // It might be an education-smb/insurance-smb style template.json (metadata only)
            // These don't have roles/verificationPipeline, skip them
        }
        catch (error) {
            logger.warn('Skipped invalid template file', { dirName, filePath, error: error instanceof Error ? error.message : String(error) });
        }
    }
    /**
     * Load a legacy flat JSON template (e.g., web-dev-team.json).
     * Converts to TeamTemplate format with sensible defaults.
     *
     * @param filePath - Absolute path to the JSON file
     * @param fileName - File name of the template
     */
    loadLegacyTemplate(filePath, fileName) {
        try {
            const content = readFileSync(filePath, 'utf-8');
            const data = JSON.parse(content);
            // Legacy format has id, name, description, members[]
            if (!data.name || !Array.isArray(data.members))
                return;
            const id = data.id ?? fileName.replace('.json', '');
            const roles = data.members.map((m, i) => ({
                role: m.role ?? 'developer',
                label: m.name ?? `Member ${i + 1}`,
                defaultName: m.name ?? `Member ${i + 1}`,
                count: 1,
                hierarchyLevel: 2,
                canDelegate: false,
                defaultSkills: [],
                promptAdditions: m.systemPrompt,
            }));
            const template = {
                id,
                name: data.name,
                description: data.description ?? '',
                category: 'custom',
                version: '0.1.0',
                hierarchical: false,
                roles,
                defaultRuntime: 'claude-code',
                verificationPipeline: {
                    name: 'Manual Review',
                    steps: [{
                            id: 'manual',
                            name: 'Manual Review',
                            description: 'Team leader reviews output manually',
                            method: 'manual_review',
                            critical: true,
                            config: {},
                        }],
                    passPolicy: 'all',
                    maxRetries: 1,
                },
            };
            this.templates.set(id, template);
        }
        catch (error) {
            logger.warn('Skipped invalid legacy template', { fileName, filePath, error: error instanceof Error ? error.message : String(error) });
        }
    }
}
//# sourceMappingURL=template.service.js.map