#!/usr/bin/env bash
# =============================================
# Medizys Local Development Runner
# =============================================
# Starts the Worker and Next.js frontend
# using concurrently in a single terminal window.

echo ""
echo -e "\e[36m=============================================\e[0m"
echo -e "\e[36m  🚀 Medizys Local Dev Server\e[0m"
echo -e "\e[36m=============================================\e[0m"
echo ""
echo -e "\e[32m  Backend             → http://localhost:8787\e[0m"
echo -e "\e[34m  Frontend            → http://localhost:3000\e[0m"
echo ""
echo -e "\e[33m  Starting unified development environment...\e[0m"
echo -e "\e[36m=============================================\e[0m"
echo ""

# Ensure Node 22 is used if nvm is available
if [ -s "$HOME/.nvm/nvm.sh" ]; then
    export NVM_DIR="$HOME/.nvm"
    source "$NVM_DIR/nvm.sh"
    nvm use 22 > /dev/null 2>&1
fi

# Cleanup zombie worker processes and ports to prevent conflicts
echo -e "\e[90m  Performing aggressive process cleanup...\e[0m"

# Define processes to kill
for procName in workerd node concurrently; do
    if pgrep -x "$procName" > /dev/null; then
        echo -e "\e[33m  Stopping $procName...\e[0m"
        pkill -f "$procName" 2>/dev/null
    fi
done

# Active verification loop to ensure all target processes have fully exited
maxWaitSeconds=5
start=$(date +%s)
while [ $(( $(date +%s) - start )) -lt $maxWaitSeconds ]; do
    if ! pgrep -f "workerd|concurrently" > /dev/null; then
        break
    fi
    sleep 0.2
done

# Function to robustly remove a directory with retry logic
remove_cache_directory() {
    local path=$1
    local label=$2
    if [ -d "$path" ]; then
        echo -e "\e[90m  Purging $label cache ($path)...\e[0m"
        rm -rf "$path"
    fi
}

# Hard clear of wrangler state and Next.js cache
remove_cache_directory "worker/.wrangler" "wrangler state"
remove_cache_directory "frontend/.next" "frontend Turbopack cache"

for port in 8787 3000; do
    echo -e "\e[90m  Ensuring port $port is free...\e[0m"
    retryCount=0
    while [ $retryCount -lt 5 ]; do
        targetPid=$(lsof -ti :$port)
        if [ ! -z "$targetPid" ]; then
            echo -e "\e[31m  Port $port occupied by PID $targetPid. Terminating...\e[0m"
            kill -9 $targetPid 2>/dev/null
            sleep 1
            retryCount=$((retryCount+1))
        else
            break
        fi
    done
done

# Disable wrangler usage metrics prompt
export WRANGLER_SEND_METRICS="false"
# Limit Node.js memory
export NODE_OPTIONS="--max-old-space-size=4096"

echo -e "\e[32mStarting development environment...\e[0m"

runnerPath="/tmp/mediqueue-dev-runner.js"

cat << 'EOF' > "$runnerPath"
const { spawn, execSync } = require('child_process');
let buffer = '';
let workerChild;
let frontendChild;
let lastRestartTime = 0;
const startTime = Date.now();

function formatSize(bytes) {
    if (bytes < 1024) return bytes + 'B';
    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
    return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
}

function formatUptime() {
    const elapsed = Math.floor((Date.now() - startTime) / 1000);
    const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
    const s = (elapsed % 60).toString().padStart(2, '0');
    const h = Math.floor(elapsed / 3600);
    if (h > 0) return `${h}h ${m}m ${s}s`;
    return `${m}m ${s}s`;
}

let lastRows = 0;
let lastCols = 0;

function updateLegend() {
    if (!process.stdout.isTTY) return;
    const rows = process.stdout.rows || 30;
    const cols = process.stdout.columns || 80;
    
    // Set scrolling region only if changed (DECSTBM homes the cursor to 1,1!)
    if (rows !== lastRows || cols !== lastCols) {
        process.stdout.write(`\x1b[1;${rows - 2}r`);
        lastRows = rows;
        lastCols = cols;
    }
    
    const lineCount = (buffer.match(/\n/g) || []).length;
    const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
    const uptimeStr = formatUptime();
    const legend1 = ` [Copy] 'c': All (${lineCount}L / ${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors `;
    const legend2 = ` [System] Uptime: ${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit `;
    
    // Draw legend
    process.stdout.write(`\x1b[s`); // save cursor
    process.stdout.write(`\x1b[${rows - 1};1H\x1b[42m\x1b[30m${legend1.padEnd(cols)}\x1b[0m`);
    process.stdout.write(`\x1b[${rows};1H\x1b[42m\x1b[30m${legend2.padEnd(cols)}\x1b[0m`);
    process.stdout.write(`\x1b[u`); // restore cursor
}

let legendInterval;
function startLegendUpdater() {
    if (legendInterval) clearInterval(legendInterval);
    legendInterval = setInterval(() => {
        updateLegend();
    }, 1000);
}

function cleanupLegend() {
    if (legendInterval) clearInterval(legendInterval);
    if (!process.stdout.isTTY) return;
    process.stdout.write('\x1b[r'); // reset scrolling region
}

process.stdout.on('resize', () => {
    if (!process.stdout.isTTY) return;
    cleanupLegend();
    console.clear();
    lastRows = 0;
    updateLegend();
});

function extractErrors(text) {
    const lines = text.split('\n');
    const errorPattern = /(error|exception|fail|⨯|✗|traceback)/i;
    let inErrorBlock = false;
    let blockStart = 0;
    const blocks = [];
    
    for (let i = 0; i < lines.length; i++) {
        if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
            if (!inErrorBlock) {
                inErrorBlock = true;
                blockStart = Math.max(0, i - 2);
            }
        } else {
            if (inErrorBlock) {
                if (lines[i].trim() === '') continue;
                const blockEnd = Math.min(lines.length - 1, i + 2);
                blocks.push(lines.slice(blockStart, blockEnd + 1).join('\n'));
                inErrorBlock = false;
            }
        }
    }
    if (inErrorBlock) {
        blocks.push(lines.slice(blockStart).join('\n'));
    }
    
    return blocks.length > 0 ? blocks.join('\n\n--- [Next Error Block] ---\n\n') : '';
}

function formatPrefix(isError, source) {
    const d = new Date();
    const ts = String(d.getHours()).padStart(2, '0') + ':' + 
               String(d.getMinutes()).padStart(2, '0') + ':' + 
               String(d.getSeconds()).padStart(2, '0');
    
    const color = source === 'worker' ? '\x1b[36m' : '\x1b[35m';
    const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
    return `\x1b[90m[${ts}]\x1b[0m ${color}${tag}\x1b[0m `;
}

function createStreamHandler(isError, source) {
    let remainder = '';
    return (d) => {
        const chunk = remainder + d.toString();
        const lines = chunk.split('\n');
        remainder = lines.pop();
        for (let line of lines) {
            line = line.replace(/\x1b\[[23]J/g, '').replace(/\x1b\[[0-9;]*H/g, '').replace(/\x1bc/g, '');
            line = line.replace(/\x1b\[[0-9]*[A-G]/g, '').replace(/\x1b\[[0-9]*[K]/g, '');
            line = line.replace(/\r/g, '');
            line = line.replace(/^\[[^\]]+\]\s*/, '');

            if (line.trim() === '') continue;

            const prefix = formatPrefix(isError, source);
            const out = prefix + line + '\n';
            
            if (isError) process.stderr.write(out);
            else process.stdout.write(out);
            buffer += out;
        }
        if (buffer.length > 1000000) {
            const idx = buffer.indexOf('\n', buffer.length - 500000);
            buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
        }
    };
}

function startWorker() {
    workerChild = spawn('npx', ['concurrently', '-c', 'cyan', '-n', 'worker', '"npm run dev --prefix worker"'], {
        env: { ...process.env, FORCE_COLOR: '1' },
        stdio: ['ignore', 'pipe', 'pipe'],
        shell: true
    });
    workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
    workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
}

function startFrontend() {
    frontendChild = spawn('npx', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"npm run dev --prefix frontend"'], {
        env: { ...process.env, FORCE_COLOR: '1' },
        stdio: ['ignore', 'pipe', 'pipe'],
        shell: true
    });
    frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
    frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
}

function startServices() {
    startWorker();
    startFrontend();
}

console.clear();
updateLegend();
startLegendUpdater();
startServices();

function copyToClipboard(text) {
    const platform = process.platform;
    let cmd = '';
    if (platform === 'darwin') cmd = 'pbcopy';
    else if (platform === 'win32') cmd = 'clip';
    else cmd = 'xclip -selection clipboard';
    try {
        execSync(cmd, { input: text });
        return true;
    } catch (e) {
        return false;
    }
}

if (process.stdin.isTTY) {
    process.stdin.setRawMode(true);
    process.stdin.resume();
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', k => {
        const now = Date.now();
        if (k === 'c' || k === 'C') {
            const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
            if (!clean.trim()) {
                console.log('\n\x1b[33m⚠️ Buffer is empty.\x1b[0m\n');
                return;
            }
            if (copyToClipboard(clean)) console.log('\n\x1b[32m📋 Copied ALL logs to clipboard!\x1b[0m\n');
            else console.log('\n\x1b[31m⚠️ Failed to copy. Ensure xclip/pbcopy is installed.\x1b[0m\n');
        } else if (k === 's' || k === 'S') {
            const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
            const shortLines = clean.split('\n').slice(-500).join('\n');
            if (copyToClipboard(shortLines)) console.log('\n\x1b[32m📋 Copied last 500 lines to clipboard!\x1b[0m\n');
            else console.log('\n\x1b[31m⚠️ Failed to copy.\x1b[0m\n');
        } else if (k === 'w' || k === 'W') {
            const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
            const workerLogs = clean.split('\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\n');
            if (copyToClipboard(workerLogs)) console.log('\n\x1b[32m📋 Copied BACKEND logs to clipboard!\x1b[0m\n');
            else console.log('\n\x1b[31m⚠️ Failed to copy.\x1b[0m\n');
        } else if (k === 'f' || k === 'F') {
            const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
            const frontendLogs = clean.split('\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\n');
            if (copyToClipboard(frontendLogs)) console.log('\n\x1b[32m📋 Copied FRONTEND logs to clipboard!\x1b[0m\n');
            else console.log('\n\x1b[31m⚠️ Failed to copy.\x1b[0m\n');
        } else if (k === 'e' || k === 'E') {
            const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
            const errors = extractErrors(clean);
            if (errors) {
                if (copyToClipboard(errors)) console.log('\n\x1b[31m🚨 Errors copied to clipboard!\x1b[0m\n');
                else console.log('\n\x1b[31m⚠️ Failed to copy errors.\x1b[0m\n');
            } else {
                console.log('\n\x1b[32m✅ No errors detected.\x1b[0m\n');
            }
        } else if (k === 'r' || k === 'R') {
            if (now - lastRestartTime < 1000) return;
            lastRestartTime = now;
            console.log('\n\x1b[33m🔄 Restarting ALL services...\x1b[0m\n');
            if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
            if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
            startServices();
        } else if (k === '1') {
            if (now - lastRestartTime < 1000) return;
            lastRestartTime = now;
            console.log('\n\x1b[33m🔄 Restarting FRONTEND...\x1b[0m\n');
            if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
            startFrontend();
        } else if (k === '2') {
            if (now - lastRestartTime < 1000) return;
            lastRestartTime = now;
            console.log('\n\x1b[33m🔄 Restarting BACKEND...\x1b[0m\n');
            if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
            startWorker();
        } else if (k === 'x' || k === 'X') {
            console.clear();
            buffer = '';
            lastRows = 0;
            updateLegend();
            console.log('\x1b[32m🧹 Terminal cleared.\x1b[0m\n');
        } else if (k === '\u0003' || k === 'q' || k === 'Q') {
            console.log('\n\x1b[33mStopping services...\x1b[0m');
            cleanupLegend();
            if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
            if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
            process.exit();
        }
    });
}

// Cleanup on unexpected exits
process.on('SIGINT', () => {
    cleanupLegend();
    if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
    if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
    process.exit();
});
process.on('exit', () => {
    cleanupLegend();
    if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
    if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
});
EOF

node "$runnerPath"
