/*
 * 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 { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { listRecords } from './orchestration-records.js';
import { getTask } from './task-state.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const DEFAULT_THRESHOLDS = {
    complexityThreshold: 70,
    cognitiveLoadThreshold: 65,
    compositeThreshold: 120,
};
const STOP_WORDS = new Set([
    'a',
    'an',
    'the',
    'and',
    'or',
    'but',
    'in',
    'on',
    'at',
    'to',
    'for',
    'of',
    'with',
    'by',
    'from',
    'is',
    'are',
    'was',
    'were',
    'be',
    'been',
    'being',
    'have',
    'has',
    'had',
    'do',
    'does',
    'did',
    'will',
    'would',
    'could',
    'should',
    'may',
    'might',
    'shall',
    'can',
    'need',
    'must',
    'it',
    'its',
    'this',
    'that',
    'these',
    'those',
    'i',
    'we',
    'you',
    'he',
    'she',
    'they',
    'me',
    'us',
    'him',
    'her',
    'them',
    'my',
    'our',
    'your',
    'his',
    'their',
    'what',
    'which',
    'who',
    'whom',
    'when',
    'where',
    'why',
    'how',
    'all',
    'each',
    'every',
    'both',
    'few',
    'more',
    'most',
    'other',
    'some',
    'such',
    'no',
    'not',
    'only',
    'same',
    'so',
    'than',
    'too',
    'very',
    'just',
    'as',
    'if',
    'then',
    'also',
    'about',
    'up',
    'out',
]);
// Estimated LOC per path entry (heuristic)
const LOC_PER_PATH = 160;
// ---------------------------------------------------------------------------
// Exported API
// ---------------------------------------------------------------------------
/**
 * Read threshold configuration from `.setup-agents/config.json`.
 * Never throws. Returns defaults if absent or malformed.
 */
export function getThresholdConfig(cwd) {
    try {
        const configPath = join(cwd, '.setup-agents', 'config.json');
        if (!existsSync(configPath))
            return { ...DEFAULT_THRESHOLDS };
        const raw = JSON.parse(readFileSync(configPath, 'utf8'));
        const section = raw['taskSplitAssessment'];
        if (!section)
            return { ...DEFAULT_THRESHOLDS };
        const thresholds = section['thresholds'];
        if (!thresholds)
            return { ...DEFAULT_THRESHOLDS };
        return {
            complexityThreshold: safeNumber(thresholds['complexityThreshold'], DEFAULT_THRESHOLDS.complexityThreshold),
            cognitiveLoadThreshold: safeNumber(thresholds['cognitiveLoadThreshold'], DEFAULT_THRESHOLDS.cognitiveLoadThreshold),
            compositeThreshold: safeNumber(thresholds['compositeThreshold'], DEFAULT_THRESHOLDS.compositeThreshold),
        };
    }
    catch {
        return { ...DEFAULT_THRESHOLDS };
    }
}
/**
 * Extract raw signals from task metadata, handoff records, and orchestra task files.
 */
export function extractTaskSplitSignals(cwd, taskId) {
    const orchestraTask = readOrchestraTask(cwd, taskId);
    const internalTask = getTask(cwd, taskId);
    const handoffs = listRecords(cwd, 'handoff').filter((h) => h.taskIds.includes(taskId));
    // Path count: from orchestra task paths array
    const paths = orchestraTask?.paths ?? [];
    const pathCount = paths.length;
    // Dependency count: from orchestra task dependencies array
    const dependencies = orchestraTask?.dependencies ?? [];
    const dependencyCount = dependencies.length;
    // Acceptance criteria: merge from orchestra task and handoff records
    const orchestraAC = orchestraTask?.acceptanceCriteria ?? [];
    const handoffAC = handoffs.flatMap((h) => h.acceptanceCriteria ?? []);
    const allAC = [...new Set([...orchestraAC, ...handoffAC])];
    const acceptanceCriteriaCount = allAC.length;
    // Role count: from internal task roles
    const roles = internalTask?.roles;
    const roleCount = roles ? new Set([...roles.required, ...roles.optional]).size : 1;
    // Phase count: from internal task declaredPhases or default
    const phaseCount = internalTask?.declaredPhases?.length ?? orchestraTask?.phases?.length ?? 1;
    // Keyword overlap ratio
    const keywordOverlapRatio = computeKeywordOverlapRatio(allAC);
    // Estimated lines of change: heuristic based on path count and AC count
    const estimatedLinesOfChange = Math.max(pathCount * LOC_PER_PATH, acceptanceCriteriaCount * 80);
    return {
        pathCount,
        dependencyCount,
        acceptanceCriteriaCount,
        roleCount,
        phaseCount,
        keywordOverlapRatio,
        estimatedLinesOfChange,
    };
}
/**
 * Compute implementation complexity score (0-100) from signals.
 */
export function computeImplementationComplexity(signals) {
    const pathScore = Math.min(signals.pathCount / 10, 1.0) * 100;
    const depScore = Math.min(signals.dependencyCount / 8, 1.0) * 100;
    const acScore = Math.min(signals.acceptanceCriteriaCount / 12, 1.0) * 100;
    const locScore = Math.min(signals.estimatedLinesOfChange / 1600, 1.0) * 100;
    return Math.round(pathScore * 0.3 + depScore * 0.25 + acScore * 0.25 + locScore * 0.2);
}
/**
 * Compute cognitive load score (0-100) from signals.
 * Note: "unresolved risks" maps to dependencyCount as a proxy (risks correlate with dependencies).
 */
export function computeCognitiveLoad(signals) {
    const roleScore = Math.min(signals.roleCount / 5, 1.0) * 100;
    const phaseScore = Math.min(signals.phaseCount / 6, 1.0) * 100;
    const overlapScore = signals.keywordOverlapRatio * 100;
    // Use dependency count as proxy for unresolved risks
    const riskScore = Math.min(signals.dependencyCount / 4, 1.0) * 100;
    return Math.round(roleScore * 0.3 + phaseScore * 0.25 + overlapScore * 0.3 + riskScore * 0.15);
}
/**
 * Full assessment: extracts signals, computes scores, determines oversized status.
 */
export function assessTaskSplit(cwd, taskId) {
    const thresholds = getThresholdConfig(cwd);
    const signals = extractTaskSplitSignals(cwd, taskId);
    const implementationComplexity = computeImplementationComplexity(signals);
    const cognitiveLoad = computeCognitiveLoad(signals);
    const combinedScore = implementationComplexity + cognitiveLoad;
    const oversized = implementationComplexity >= thresholds.complexityThreshold ||
        cognitiveLoad >= thresholds.cognitiveLoadThreshold ||
        combinedScore >= thresholds.compositeThreshold;
    const splitReasons = [];
    if (implementationComplexity >= thresholds.complexityThreshold) {
        splitReasons.push(`Implementation complexity ${implementationComplexity} exceeds threshold ${thresholds.complexityThreshold}`);
    }
    if (cognitiveLoad >= thresholds.cognitiveLoadThreshold) {
        splitReasons.push(`Cognitive load ${cognitiveLoad} exceeds threshold ${thresholds.cognitiveLoadThreshold}`);
    }
    if (combinedScore >= thresholds.compositeThreshold) {
        splitReasons.push(`Combined score ${combinedScore} exceeds composite threshold ${thresholds.compositeThreshold}`);
    }
    return {
        taskId,
        implementationComplexity,
        cognitiveLoad,
        combinedScore,
        oversized,
        splitReasons,
        signals,
    };
}
function readOrchestraTask(cwd, taskId) {
    try {
        const tasksPath = join(cwd, '.agent-workflow', 'tasks.json');
        if (!existsSync(tasksPath))
            return undefined;
        const tasks = JSON.parse(readFileSync(tasksPath, 'utf8'));
        return tasks.find((t) => t.id === taskId);
    }
    catch {
        return undefined;
    }
}
/**
 * Keyword overlap heuristic:
 * 1. Tokenize each AC into lowercase words, remove stop words
 * 2. Build bag-of-words per AC
 * 3. Compute pairwise Jaccard similarity
 * 4. keywordOverlapRatio = 1 - averageJaccardSimilarity
 * 5. If fewer than 2 ACs: keywordOverlapRatio = 0
 */
function computeKeywordOverlapRatio(acceptanceCriteria) {
    if (acceptanceCriteria.length < 2)
        return 0;
    const bags = acceptanceCriteria.map(tokenize);
    let totalSimilarity = 0;
    let pairCount = 0;
    for (let i = 0; i < bags.length; i++) {
        for (let j = i + 1; j < bags.length; j++) {
            totalSimilarity += jaccardSimilarity(bags[i], bags[j]);
            pairCount++;
        }
    }
    const averageSimilarity = pairCount > 0 ? totalSimilarity / pairCount : 0;
    return Math.round((1 - averageSimilarity) * 1000) / 1000;
}
function tokenize(text) {
    const words = text
        .toLowerCase()
        .replace(/[^a-z0-9\s]/g, ' ')
        .split(/\s+/)
        .filter((w) => w.length > 1 && !STOP_WORDS.has(w));
    return new Set(words);
}
function jaccardSimilarity(a, b) {
    if (a.size === 0 && b.size === 0)
        return 1;
    let intersection = 0;
    for (const word of a) {
        if (b.has(word))
            intersection++;
    }
    const union = a.size + b.size - intersection;
    return union === 0 ? 0 : intersection / union;
}
function safeNumber(value, fallback) {
    if (typeof value === 'number' && Number.isFinite(value))
        return value;
    return fallback;
}
//# sourceMappingURL=task-split-assessment.js.map