/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { execFileSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { basename, join } from 'node:path';
import { classCatalogExists, readClassCatalog, writeClassCatalog } from './project-knowledge.js';
export function syncClassCatalog(cwd, taskId) {
    if (!classCatalogExists(cwd))
        return 0;
    const changedFiles = getTaskChangedFiles(cwd, taskId);
    if (changedFiles.length === 0)
        return 0;
    const newEntries = changedFiles
        .map((filePath) => classifyFile(cwd, filePath))
        .filter((e) => e !== null);
    if (newEntries.length === 0)
        return 0;
    const catalog = readClassCatalog(cwd);
    const updated = mergeEntries(catalog, newEntries);
    if (updated === catalog)
        return 0;
    writeClassCatalog(cwd, updated);
    return newEntries.length;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function getTaskChangedFiles(cwd, taskId) {
    try {
        const output = execFileSync('git', ['diff', '--name-only', 'HEAD~1', 'HEAD'], { cwd, encoding: 'utf8' });
        return output
            .split('\n')
            .filter((f) => f.trim().length > 0)
            .filter((f) => f.endsWith('.cls') || f.endsWith('.trigger') || f.includes('/lwc/'));
    }
    catch {
        return [];
    }
}
export function classifyFile(cwd, filePath) {
    const fullPath = join(cwd, filePath);
    if (!existsSync(fullPath))
        return null;
    const name = basename(filePath).replace(/\.(cls|trigger)$/, '');
    if (filePath.includes('/lwc/')) {
        const lwcName = filePath.split('/lwc/')[1]?.split('/')[0];
        if (!lwcName)
            return null;
        return { name: lwcName, category: 'lwc', detail: detectLwcTarget(cwd, filePath) };
    }
    if (filePath.endsWith('.trigger')) {
        const objectName = detectTriggerObject(cwd, fullPath);
        return { name, category: 'trigger-handler', detail: objectName };
    }
    const head = readFileHead(fullPath, 15);
    if (isTestClass(name, head)) {
        return { name, category: 'test', detail: inferTestTarget(name) };
    }
    if (isInvocableOrAsync(head)) {
        return { name, category: 'invocable', detail: detectInvocableType(head) };
    }
    if (isTriggerHandler(head)) {
        const objectName = inferHandlerObject(name);
        return { name, category: 'trigger-handler', detail: objectName };
    }
    if (isServiceClass(name, head)) {
        return { name, category: 'service', detail: inferServicePurpose(name) };
    }
    if (isDomainClass(name, head)) {
        return { name, category: 'domain', detail: inferDomainObject(name) };
    }
    return { name, category: 'utility', detail: '' };
}
export function mergeEntries(catalog, entries) {
    let updated = catalog;
    for (const entry of entries) {
        if (updated.includes(`| ${entry.name} |`))
            continue;
        const section = categoryToSection(entry.category);
        const sectionIdx = updated.indexOf(section);
        if (sectionIdx === -1)
            continue;
        const tableEnd = findTableEnd(updated, sectionIdx);
        const row = buildRow(entry);
        updated = updated.slice(0, tableEnd) + row + '\n' + updated.slice(tableEnd);
    }
    return updated;
}
function categoryToSection(category) {
    const map = {
        domain: '## Domain Classes',
        service: '## Service Classes',
        'trigger-handler': '## Trigger Handlers',
        test: '## Test Classes',
        invocable: '## Invocable & REST',
        lwc: '## LWC Components',
        utility: '## Utilities',
    };
    return map[category];
}
function buildRow(entry) {
    switch (entry.category) {
        case 'domain':
            return `| ${entry.name} | ${entry.detail} | |`;
        case 'service':
            return `| ${entry.name} | ${entry.detail} |`;
        case 'trigger-handler':
            return `| | ${entry.detail} | ${entry.name} |`;
        case 'test':
            return `| ${entry.name} | ${entry.detail} |`;
        case 'invocable':
            return `| ${entry.name} | ${entry.detail} | |`;
        case 'lwc':
            return `| ${entry.name} | ${entry.detail} |`;
        case 'utility':
            return `| ${entry.name} | ${entry.detail} |`;
    }
}
function findTableEnd(content, sectionStart) {
    const lines = content.slice(sectionStart).split('\n');
    let offset = sectionStart;
    let inTable = false;
    for (const line of lines) {
        if (line.startsWith('|')) {
            inTable = true;
        }
        else if (inTable && !line.startsWith('|')) {
            return offset;
        }
        offset += line.length + 1;
    }
    return offset;
}
function readFileHead(filePath, lines) {
    try {
        const content = readFileSync(filePath, 'utf8');
        return content.split('\n').slice(0, lines).join('\n');
    }
    catch {
        return '';
    }
}
function isTestClass(name, head) {
    return name.endsWith('_Test') || name.endsWith('Test') || head.includes('@isTest') || head.includes('@IsTest');
}
function isTriggerHandler(head) {
    return (head.includes('extends TriggerHandler') ||
        head.includes('implements TriggerHandler') ||
        head.includes('TriggerHandler.') ||
        (head.includes('Handler') && head.includes('override')));
}
function isInvocableOrAsync(head) {
    return (head.includes('@InvocableMethod') ||
        head.includes('@InvocableVariable') ||
        head.includes('@RestResource') ||
        head.includes('implements Queueable') ||
        head.includes('implements Database.Batchable') ||
        head.includes('implements Schedulable'));
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function isServiceClass(name, head) {
    return name.includes('Service') || name.includes('Orchestrator');
}
function isDomainClass(name, head) {
    return name.includes('Domain') || head.includes('SObject') || head.includes('__c');
}
function inferTestTarget(name) {
    return name.replace(/_Test$|Test$/, '');
}
function inferHandlerObject(name) {
    return name.replace(/TriggerHandler$|Handler$/, '').replace(/^.*_/, '');
}
function inferServicePurpose(name) {
    return name.replace(/Service$|Orchestrator$/, '').replace(/^[A-Z]+_/, '');
}
function inferDomainObject(name) {
    return name.replace(/Domain$/, '').replace(/^[A-Z]+_/, '');
}
function detectTriggerObject(cwd, triggerPath) {
    try {
        const content = readFileSync(triggerPath, 'utf8');
        const match = /trigger\s+\w+\s+on\s+(\w+)/.exec(content);
        return match?.[1] ?? '';
    }
    catch {
        return '';
    }
}
function detectLwcTarget(cwd, filePath) {
    const lwcDir = filePath.split('/lwc/')[1]?.split('/')[0];
    if (!lwcDir)
        return '';
    const metaPath = join(cwd, `force-app/main/default/lwc/${lwcDir}/${lwcDir}.js-meta.xml`);
    try {
        const meta = readFileSync(metaPath, 'utf8');
        const targetMatch = /<target>(.*?)<\/target>/.exec(meta);
        return targetMatch?.[1] ?? '';
    }
    catch {
        return '';
    }
}
function detectInvocableType(head) {
    if (head.includes('@InvocableMethod'))
        return 'InvocableMethod';
    if (head.includes('@RestResource'))
        return 'REST';
    if (head.includes('implements Queueable'))
        return 'Queueable';
    if (head.includes('implements Database.Batchable'))
        return 'Batch';
    if (head.includes('implements Schedulable'))
        return 'Schedulable';
    return 'Invocable';
}
//# sourceMappingURL=class-catalog-sync.js.map