/**
 * Task relationship management core module.
 * @task T4538
 * @epic T4454
 */
import { ExitCode } from '@cleocode/contracts';
import { CleoError } from '../errors.js';
import { getAccessor } from '../store/data-accessor.js';
/** Suggest related tasks based on shared attributes. */
export async function suggestRelated(taskId, opts, accessor) {
    const acc = accessor ?? (await getAccessor(opts.cwd));
    const { tasks: allTasks } = await acc.queryTasks({});
    const task = allTasks.find((t) => t.id === taskId);
    if (!task) {
        throw new CleoError(ExitCode.NOT_FOUND, `Task ${taskId} not found`, {
            fix: `cleo find "${taskId}"`,
            details: { field: 'taskId', actual: taskId },
        });
    }
    const suggestions = [];
    for (const other of allTasks) {
        if (other.id === taskId)
            continue;
        let score = 0;
        const reasons = [];
        // Shared labels
        const sharedLabels = (task.labels ?? []).filter((l) => (other.labels ?? []).includes(l));
        if (sharedLabels.length > 0) {
            score += sharedLabels.length * 20;
            reasons.push(`Shared labels: ${sharedLabels.join(', ')}`);
        }
        // Same phase
        if (task.phase && task.phase === other.phase) {
            score += 15;
            reasons.push(`Same phase: ${task.phase}`);
        }
        // Same parent
        if (task.parentId && task.parentId === other.parentId) {
            score += 25;
            reasons.push('Same parent');
        }
        const threshold = opts.threshold ?? 50;
        if (score >= threshold) {
            suggestions.push({
                id: other.id,
                title: other.title,
                score: Math.min(score, 100),
                reason: reasons.join('; '),
            });
        }
    }
    return {
        taskId,
        suggestions: suggestions.sort((a, b) => b.score - a.score).slice(0, 10),
        count: suggestions.length,
    };
}
/** Add a relation between tasks. */
export async function addRelation(from, to, type, reason, cwd, accessor) {
    const acc = accessor ?? (await getAccessor(cwd));
    const fromExists = await acc.taskExists(from);
    if (!fromExists) {
        throw new CleoError(ExitCode.NOT_FOUND, `Task ${from} not found`, {
            fix: `cleo find "${from}"`,
            details: { field: 'from', actual: from },
        });
    }
    const toExists = await acc.taskExists(to);
    if (!toExists) {
        throw new CleoError(ExitCode.NOT_FOUND, `Task ${to} not found`, {
            fix: `cleo find "${to}"`,
            details: { field: 'to', actual: to },
        });
    }
    // Persist to task_relations table via accessor (T5168 fix)
    await acc.addRelation(from, to, type, reason);
    return { from, to, type, reason, added: true };
}
/** Discover related tasks using various methods. */
export async function discoverRelated(taskId, cwd, accessor) {
    return suggestRelated(taskId, { threshold: 30, cwd }, accessor);
}
/** List existing relations for a task. */
export async function listRelations(taskId, cwd, accessor) {
    const acc = accessor ?? (await getAccessor(cwd));
    const task = await acc.loadSingleTask(taskId);
    if (!task) {
        throw new CleoError(ExitCode.NOT_FOUND, `Task ${taskId} not found`, {
            fix: `cleo find "${taskId}"`,
            details: { field: 'taskId', actual: taskId },
        });
    }
    // task.relates is populated from task_relations table by loadSingleTask
    const relates = task.relates ?? [];
    return {
        taskId,
        relations: relates,
        count: relates.length,
    };
}
//# sourceMappingURL=relates.js.map