";
document.getElementById('tdm-results-area').style.display = 'block';
return;
}
// Kahn's Algorithm for Topological Sort
const inDegree = new Map();
const adjList = new Map(); // Stores tasks that depend on a given task
tasks.forEach(task => {
inDegree.set(task.id, 0);
adjList.set(task.id, []);
});
tasks.forEach(task => {
task.prerequisites.forEach(prereqId => {
// If task depends on prereqId, then an edge from prereqId to task.id
if (adjList.has(prereqId)) { // prereqId must exist
adjList.get(prereqId).push(task.id);
inDegree.set(task.id, (inDegree.get(task.id) || 0) + 1);
} else {
// This case implies a prerequisite was selected that no longer exists or was never valid.
// Should ideally be caught by tdm_updatePrerequisiteOptions logic ensuring valid IDs.
console.warn(`Prerequisite ${prereqId} for task ${task.id} not found in defined tasks.`);
}
});
});
const queue = [];
tasks.forEach(task => {
if (inDegree.get(task.id) === 0) {
queue.push(task.id);
}
});
const sortedOrder = [];
const levelMap = new Map(); // To store level of each task
let currentLevel = 0;
while (queue.length > 0) {
const tasksAtCurrentLevel = queue.length;
if (tasksAtCurrentLevel === 0 && sortedOrder.length < tasks.length) break; // Cycle detected or disconnected
for (let i = 0; i < tasksAtCurrentLevel; i++) {
const u = queue.shift();
sortedOrder.push(u);
levelMap.set(u, currentLevel);
(adjList.get(u) || []).forEach(v_id => {
inDegree.set(v_id, (inDegree.get(v_id) || 0) - 1);
if (inDegree.get(v_id) === 0) {
queue.push(v_id);
}
});
}
currentLevel++;
}
const outputDiv = document.getElementById('tdm-analysis-output');
outputDiv.innerHTML = ''; // Clear previous results
if (sortedOrder.length === tasks.length) {
outputDiv.innerHTML += "
Valid task sequence found:
";
const ol = document.createElement('ol');
sortedOrder.forEach(taskId => {
const taskObj = tasks.find(t => t.id === taskId);
const li = document.createElement('li');
li.textContent = `${taskObj.id}: ${taskObj.name} (Level: ${levelMap.get(taskId)})`;
ol.appendChild(li);
});
outputDiv.appendChild(ol);
tdm_analysisResultForPdf = { success: true, tasks, sortedOrder, levelMap, reportDate: new Date().toLocaleDateString() };
} else {
outputDiv.innerHTML += "
Circular dependency detected or disconnected tasks found. Unable to determine a complete valid sequence.
";
let problematicTasksInfo = "
Tasks not included in the valid sequence (potential cycle involvement or unresolvable dependencies):
";
tasks.forEach(task => {
if (!sortedOrder.includes(task.id)) {
problematicTasksInfo += `- ${task.id}: ${task.name} (Still needs ${inDegree.get(task.id)} prerequisites)
`;
}
});
problematicTasksInfo += "
";
outputDiv.innerHTML += problematicTasksInfo;
tdm_analysisResultForPdf = { success: false, tasks, sortedOrder, inDegree, reportDate: new Date().toLocaleDateString() };
}
document.getElementById('tdm-results-area').style.display = 'block';
document.getElementById('tdm-download-pdf-button').disabled = false;
}
function tdm_clearAll() {
document.getElementById('tdm-tasks-list').innerHTML = '';
tdm_taskCounter = 0;
tdm_definedTasks = [];
tdm_addTaskEntry(); // Add one default task
tdm_disablePdfAndClearResults();
tdm_openTab({currentTarget: document.querySelector('.tdm-tab-button[onclick*="tdm-define-tab"]')}, 'tdm-define-tab');
}
function tdm_downloadPDF() {
if (!tpc_jsPDF_loaded_TDM || !tpc_autoTable_loaded_TDM) {
alert("TDM Error: PDF libraries not fully loaded. Cannot generate PDF.");
return;
}
if (!tdm_analysisResultForPdf) {
alert("Please analyze dependencies first before downloading PDF.");
return;
}
const { success, tasks, sortedOrder, levelMap, inDegree, reportDate } = tdm_analysisResultForPdf;
const doc = new TPC_jsPDF_TDM();
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim();
const darkColor = getComputedStyle(document.documentElement).getPropertyValue('--dark-color').trim();
const dangerColor = getComputedStyle(document.documentElement).getPropertyValue('--danger-color').trim();
const successColor = getComputedStyle(document.documentElement).getPropertyValue('--accent-color').trim();
let finalY = 15;
doc.setFontSize(18);
doc.setTextColor(primaryColor);
doc.text("Task Dependency Analysis Report", doc.internal.pageSize.getWidth() / 2, finalY, { align: 'center' });
finalY += 8;
doc.setFontSize(10);
doc.setTextColor(darkColor);
doc.text(`Report Date: ${reportDate}`, doc.internal.pageSize.getWidth() / 2, finalY, { align: 'center'});
finalY += 10;
doc.setFontSize(14);
doc.setTextColor(primaryColor);
doc.text("Defined Tasks & Prerequisites", 14, finalY);
finalY += 7;
const taskTableBody = tasks.map(t => [
t.id,
t.name,
t.prerequisites.join(', ') || 'None'
]);
doc.autoTable({
startY: finalY,
head: [['ID', 'Task Name', 'Prerequisites']],
body: taskTableBody,
theme: 'grid',
headStyles: { fillColor: primaryColor, textColor: '#ffffff', fontSize: 10 },
styles: { fontSize: 9, cellPadding: 2, overflow: 'linebreak' }
});
finalY = doc.lastAutoTable.finalY + 10;
doc.setFontSize(14);
doc.setTextColor(primaryColor);
doc.text("Analysis Results", 14, finalY);
finalY += 7;
if (success) {
doc.setFontSize(12);
doc.setTextColor(successColor);
doc.text("Status: Valid task sequence found.", 14, finalY);
finalY += 6;
doc.setTextColor(darkColor);
doc.text("Execution Order (by Level):", 14, finalY);
finalY += 6;
const orderedTaskDetails = sortedOrder.map((taskId, index) => {
const taskObj = tasks.find(t => t.id === taskId);
return [`${index + 1}. ${taskId}`, taskObj.name, levelMap.get(taskId).toString()];
});
doc.autoTable({
startY: finalY,
head: [['Order', 'Task Name', 'Level']],
body: orderedTaskDetails,
theme: 'grid',
headStyles: {fillColor: successColor, textColor: '#ffffff', fontSize:10},
styles: { fontSize: 9, cellPadding: 2 }
});
} else {
doc.setFontSize(12);
doc.setTextColor(dangerColor);
doc.text("Status: Circular dependency detected or tasks are disconnected.", 14, finalY);
finalY += 6;
doc.setTextColor(darkColor);
doc.text("Problematic tasks (not in valid sequence or with remaining dependencies):", 14, finalY);
finalY += 6;
let problematicDetails = [];
tasks.forEach(task => {
if (!sortedOrder || !sortedOrder.includes(task.id)) { // Ensure sortedOrder is defined
problematicDetails.push([task.id, task.name, `Needs ${inDegree ? (inDegree.get(task.id) || 0) : 'N/A'} prereqs`]);
}
});
if (problematicDetails.length > 0) {
doc.autoTable({
startY: finalY,
head: [['ID', 'Task Name', 'Status']],
body: problematicDetails,
theme: 'grid',
headStyles: {fillColor: dangerColor, textColor: '#ffffff', fontSize:10},
styles: { fontSize: 9, cellPadding: 2 }
});
} else {
doc.text("Could not identify specific problematic tasks (general graph error).", 14, finalY);
}
}
doc.save(`Task_Dependency_Analysis_${new Date().toISOString().slice(0,10)}.pdf`);
}