import { ConfigurationManager } from './managers/ConfigurationManager.js';
import { TabManager } from './managers/TabManager.js';
import { ResultsManager, HelpSystem } from './managers/ResultsManager.js';
import { SplitViewManager } from './managers/SplitViewManager.js';
import {
escapeHtml,
examples,
validateFileSize,
formatFileSize,
clamp,
MAX_TEXT_FILE_SIZE
} from './utils/helpers.js';
import { extractBamHeader, isBamFile } from './utils/headerExtractor.js';
const configManager = new ConfigurationManager();
const tabManager = new TabManager();
const resultsManager = new ResultsManager();
const helpSystem = new HelpSystem();
const splitViewManager = new SplitViewManager();
window.configManager = configManager;
window.tabManager = tabManager;
window.resultsManager = resultsManager;
window.helpSystem = helpSystem;
window.splitViewManager = splitViewManager;
function showTab(tabId) {
tabManager.switchTab(tabId);
}
function toggleHelp() {
helpSystem.toggle();
}
function showHelpTab(tabId) {
helpSystem.showTab(tabId);
}
function updateThreshold(value) {
const parsed = parseInt(value, 10);
const validated = isNaN(parsed) ? configManager.scoreThreshold : clamp(parsed, 0, 100);
configManager.scoreThreshold = validated;
document.getElementById('threshold-value').textContent = validated + '%';
configManager.saveConfig();
}
function updateWeight(type, value) {
const mapping = {
'md5-jaccard': 'md5Jaccard',
'name-length': 'nameLength',
'md5-coverage': 'md5Coverage',
'order-score': 'orderScore'
};
const key = mapping[type];
if (!key) return;
const parsed = parseInt(value, 10);
const validated = isNaN(parsed) ? configManager.scoringWeights[key] : clamp(parsed, 0, 100);
configManager.scoringWeights[key] = validated;
document.getElementById(type + '-value').textContent = validated + '%';
configManager.saveConfig();
}
function toggleAdvanced() {
const options = document.getElementById('advanced-options');
const arrow = document.getElementById('advanced-arrow');
if (options.classList.contains('expanded')) {
options.classList.remove('expanded');
arrow.textContent = 'â–¶';
} else {
options.classList.add('expanded');
arrow.textContent = 'â–¼';
}
}
function toggleResultDetails(index) {
resultsManager.toggleExpansion(index);
}
function toggleInputSection() {
const inputSection = document.getElementById('input-section');
const arrow = document.getElementById('input-collapse-arrow');
if (inputSection.classList.contains('collapsed')) {
inputSection.classList.remove('collapsed');
arrow.textContent = 'â–¼';
} else {
inputSection.classList.add('collapsed');
arrow.textContent = 'â–²';
}
}
function collapseInputAfterIdentify() {
const inputSection = document.getElementById('input-section');
const arrow = document.getElementById('input-collapse-arrow');
inputSection.classList.add('collapsed');
arrow.textContent = 'â–²';
}
function showUploadError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'error';
errorDiv.style.cssText = 'position: fixed; top: 20px; right: 20px; background: var(--error); color: white; padding: 1rem; border-radius: 6px; z-index: 10000; max-width: 400px;';
errorDiv.textContent = message;
document.body.appendChild(errorDiv);
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.parentNode.removeChild(errorDiv);
}
}, 5000);
}
function showExtractionStatus(message) {
let statusDiv = document.getElementById('extraction-status');
if (!statusDiv) {
statusDiv = document.createElement('div');
statusDiv.id = 'extraction-status';
statusDiv.className = 'extraction-status';
const preview = document.getElementById('binary-preview');
if (preview && preview.parentNode) {
preview.parentNode.insertBefore(statusDiv, preview);
}
}
statusDiv.textContent = message;
statusDiv.style.display = 'block';
}
function hideExtractionStatus() {
const statusDiv = document.getElementById('extraction-status');
if (statusDiv) {
statusDiv.style.display = 'none';
}
}
async function handleFileUpload(input, format) {
const file = input.files[0];
if (!file) return;
if (format === 'binary' && isBamFile(file.name)) {
try {
showExtractionStatus('Extracting header from BAM file...');
const result = await extractBamHeader(file);
if (result) {
const headerBlob = new Blob([result.header], { type: 'text/plain' });
const headerFile = new File(
[headerBlob], file.name + '.header.sam', { type: 'text/plain' }
);
tabManager.setCurrentFile(headerFile, 'text');
document.getElementById('binary-preview').style.display = 'block';
document.getElementById('binary-filename').textContent =
`${file.name} (header extracted, ${formatFileSize(result.header.length)})`;
hideExtractionStatus();
tabManager.validateFormat();
return;
}
} catch (err) {
console.warn('Client-side BAM header extraction failed, falling back to upload:', err);
hideExtractionStatus();
}
}
if (format !== 'binary') {
const validation = validateFileSize(file, MAX_TEXT_FILE_SIZE);
if (!validation.valid) {
showUploadError(validation.error);
input.value = ''; return;
}
}
tabManager.currentFile = file;
tabManager.currentFormat = format;
if (['text', 'assembly', 'vcf'].includes(format)) {
const reader = new FileReader();
reader.onload = function(e) {
const content = e.target.result;
if (format === 'text') {
document.getElementById('header-input').value = content;
tabManager.validateFormat();
} else if (format === 'assembly') {
document.getElementById('assembly-input').value = content;
tabManager.validateFormat();
} else if (format === 'vcf') {
document.getElementById('vcf-input').value = content;
tabManager.validateFormat();
}
};
reader.readAsText(file);
} else if (format === 'binary') {
document.getElementById('binary-preview').style.display = 'block';
document.getElementById('binary-filename').textContent = file.name;
tabManager.validateFormat();
}
}
function loadExample(tabType) {
const example = examples[tabType];
if (!example) {
return;
}
let textareaId;
switch (tabType) {
case 'sam-dict':
textareaId = 'header-input';
break;
case 'assembly-report':
textareaId = 'assembly-input';
break;
case 'vcf':
textareaId = 'vcf-input';
break;
default:
return;
}
const textarea = document.getElementById(textareaId);
if (textarea) {
textarea.value = example;
tabManager.validateFormat();
}
updateThreshold(0);
const slider = document.getElementById('score-threshold');
if (slider) {
slider.value = 0;
}
}
function clearTextArea(textareaId) {
const textarea = document.getElementById(textareaId);
if (textarea) {
textarea.value = '';
tabManager.validateFormat();
}
}
window.showTab = showTab;
window.toggleHelp = toggleHelp;
window.showHelpTab = showHelpTab;
window.updateThreshold = updateThreshold;
window.updateWeight = updateWeight;
window.toggleAdvanced = toggleAdvanced;
window.toggleResultDetails = toggleResultDetails;
window.toggleInputSection = toggleInputSection;
window.collapseInputAfterIdentify = collapseInputAfterIdentify;
window.handleFileUpload = handleFileUpload;
window.loadExample = loadExample;
window.clearTextArea = clearTextArea;
window.escapeHtml = escapeHtml;
document.addEventListener('DOMContentLoaded', function() {
configManager.updateUI();
tabManager.validateFormat();
const textareas = ['header-input', 'assembly-input', 'vcf-input'];
textareas.forEach(id => {
const textarea = document.getElementById(id);
if (textarea) {
textarea.addEventListener('input', function() {
tabManager.validateFormat();
});
textarea.addEventListener('paste', function() {
setTimeout(() => tabManager.validateFormat(), 10);
});
}
});
const resultLimitEl = document.getElementById('result-limit');
if (resultLimitEl) {
resultLimitEl.addEventListener('change', function() {
configManager.resultLimit = parseInt(this.value, 10);
configManager.saveConfig();
});
}
const identifyForm = document.getElementById('identify-form');
if (identifyForm) {
identifyForm.addEventListener('submit', async function(e) {
e.preventDefault();
collapseInputAfterIdentify();
const input = tabManager.getCurrentInput();
if (!input) {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '<div class="error">No valid input found. Please enter text or upload a file.</div>';
return;
}
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '<div class="loading">Analyzing...</div>';
try {
const formData = new FormData();
if (input.type === 'text') {
formData.append('header_text', input.content);
if (input.filename) {
formData.append('filename', input.filename);
}
} else if (input.type === 'file') {
formData.append('file', input.file);
}
const config = configManager.getConfig();
formData.append('config', JSON.stringify(config));
const response = await fetch('/api/identify', {
method: 'POST',
body: formData
});
if (!response.ok) {
const responseText = await response.text();
throw new Error(`HTTP ${response.status}: ${response.statusText} - ${responseText}`);
}
let data;
try {
data = await response.json();
} catch (jsonError) {
throw new Error(`Invalid JSON response from server: ${jsonError.message}`);
}
splitViewManager.originalInput = input;
splitViewManager.originalConfig = config;
resultsManager.renderResults(data, config);
} catch (error) {
resultsDiv.innerHTML = `<div class="error">Error: ${escapeHtml(error.message)}</div>`;
}
});
}
});
export {
configManager,
tabManager,
resultsManager,
helpSystem,
splitViewManager,
showTab,
toggleHelp,
showHelpTab,
updateThreshold,
updateWeight,
toggleAdvanced,
toggleResultDetails,
toggleInputSection,
collapseInputAfterIdentify,
handleFileUpload,
loadExample,
clearTextArea
};