"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.startWatcher = startWatcher;
exports.stopWatcher = stopWatcher;
exports.getWatcherStatus = getWatcherStatus;
exports.getActiveWatchers = getActiveWatchers;
exports.restoreActiveWatchers = restoreActiveWatchers;
exports.getLiveEvents = getLiveEvents;
exports.logRefresh = logRefresh;
exports.getRefreshHistory = getRefreshHistory;
exports.checkIfNeedsRefresh = checkIfNeedsRefresh;
exports.performAutoRefresh = performAutoRefresh;
exports.cleanupOldEvents = cleanupOldEvents;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const database_1 = require("../../06-memory/database/database");
const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage', '__pycache__', '.fivo', 'archived', '.vscode', '.idea']);
const activeWatchers = new Map();
function shouldIgnore(filePath) {
    const parts = filePath.split(path.sep);
    return parts.some(p => IGNORE_DIRS.has(p) || p.startsWith('.'));
}
function classifyEvent(filePath, eventType) {
    const base = path.basename(filePath);
    if (base === 'package.json' || base === 'package-lock.json' || base === 'pnpm-lock.yaml' || base === 'yarn.lock') {
        return 'package_change';
    }
    if (eventType === 'rename' && !fs.existsSync(filePath))
        return 'file_delete';
    if (eventType === 'rename' && fs.existsSync(filePath))
        return 'file_create';
    return 'file_save';
}
function recordEvent(project, eventType, filePath, metadata = {}) {
    const db = (0, database_1.getDb)();
    // Keep only last 500 events per project to avoid bloat
    const result = db.prepare(`
    INSERT INTO live_events (project, event_type, file_path, metadata)
    VALUES (?, ?, ?, ?)
  `).run(project, eventType, filePath, JSON.stringify(metadata));
    const id = Number(result.lastInsertRowid);
    // Update watcher state
    db.prepare(`
    UPDATE watcher_state SET event_count = event_count + 1, last_event_at = datetime('now')
    WHERE project = ?
  `).run(project);
    // Also touch session_files (manual upsert since table has no unique constraint)
    if (eventType === 'file_save' || eventType === 'file_create') {
        const existing = db.prepare(`
      SELECT id, touch_count FROM session_files
      WHERE project = ? AND file_path = ?
      ORDER BY id DESC LIMIT 1
    `).get(project, filePath);
        if (existing) {
            db.prepare(`
        UPDATE session_files
        SET touch_count = touch_count + 1, last_touched = datetime('now')
        WHERE id = ?
      `).run(existing.id);
        }
        else {
            db.prepare(`
        INSERT INTO session_files (project, file_path, touch_count, first_touched, last_touched)
        VALUES (?, ?, 1, datetime('now'), datetime('now'))
      `).run(project, filePath);
        }
    }
    // Record to memory_events for long-term memory (Phase 2A integration)
    try {
        const { recordMemoryEvent } = require('../../06-memory/stores/memory-event-store');
        const ext = path.extname(filePath).toLowerCase();
        const topic = getTopicFromPath(filePath);
        recordMemoryEvent({
            project,
            type: 'file_event',
            topic,
            summary: `${eventType}: ${path.relative(process.cwd(), filePath)}`,
            files: [filePath],
            importance: eventType === 'package_change' ? 4 : eventType === 'file_delete' ? 3 : 1,
            details: { eventType, ext, ...metadata },
        });
        // Handoff auto-trigger: update on hot files / repeated edits / important changes
        try {
            triggerHandoffAutoUpdate(project, filePath, eventType);
        }
        catch { /* skip */ }
    }
    catch { /* memory store not initialized yet — skip */ }
    return { id };
}
function getTopicFromPath(filePath) {
    const base = path.basename(filePath);
    if (base === 'package.json' || base === 'package-lock.json')
        return 'dependencies';
    if (base.endsWith('.test.ts') || base.endsWith('.test.js') || base.endsWith('.spec.ts'))
        return 'testing';
    if (base.endsWith('.ts') || base.endsWith('.js'))
        return 'code';
    if (base.endsWith('.md'))
        return 'documentation';
    if (base.endsWith('.json'))
        return 'config';
    if (base.endsWith('.css') || base.endsWith('.scss'))
        return 'styles';
    return 'other';
}
const HANDOFF_TOUCH_THRESHOLD = 10;
const HOT_FILE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx']);
let _handoffTouchCounts = new Map();
function triggerHandoffAutoUpdate(project, filePath, eventType) {
    const ext = path.extname(filePath).toLowerCase();
    if (!HOT_FILE_EXTENSIONS.has(ext))
        return;
    const key = `${project}:${filePath}`;
    const count = (_handoffTouchCounts.get(key) || 0) + 1;
    _handoffTouchCounts.set(key, count);
    if (count % HANDOFF_TOUCH_THRESHOLD !== 0 && eventType !== 'file_delete')
        return;
    try {
        const cwd = process.cwd();
        const { saveHandoffEntry } = require('../../06-memory/handoff/handoff-store');
        const relPath = path.relative(cwd, filePath);
        saveHandoffEntry(cwd, {
            project,
            title: `Auto: ${relPath}`,
            summary: count >= HANDOFF_TOUCH_THRESHOLD
                ? `Hot file ${relPath} edited ${count}+ times`
                : `${eventType} ${relPath}`,
            why: 'auto-triggered by watcher on repeated edits',
            filesChanged: [filePath],
            status: 'done',
        });
    }
    catch { /* skip */ }
}
function startWatcher(project, dirPath, opts = {}) {
    if (activeWatchers.has(project)) {
        return { started: false, message: 'Watcher already running for this project' };
    }
    if (!fs.existsSync(dirPath)) {
        return { started: false, message: `Directory does not exist: ${dirPath}` };
    }
    const db = (0, database_1.getDb)();
    const watchers = [];
    const recursiveWatchers = new Map();
    const debounceTimers = new Map();
    // Register watcher state. Restored watchers preserve historical event counts.
    if (opts.preserveState) {
        db.prepare(`
      UPDATE watcher_state
      SET active = 1, dir_path = ?, started_at = datetime('now')
      WHERE project = ?
    `).run(dirPath, project);
    }
    else {
        db.prepare(`
      INSERT INTO watcher_state (project, dir_path, active, started_at)
      VALUES (?, ?, 1, datetime('now'))
      ON CONFLICT(project) DO UPDATE SET
        dir_path = excluded.dir_path,
        active = 1,
        event_count = 0,
        last_event_at = NULL,
        started_at = datetime('now')
    `).run(project, dirPath);
    }
    // Top-level watcher
    const topWatcher = fs.watch(dirPath, { persistent: false }, (eventType, filename) => {
        if (!filename)
            return;
        const fullPath = path.join(dirPath, filename);
        if (shouldIgnore(fullPath))
            return;
        // Debounce 100ms
        const existing = debounceTimers.get(fullPath);
        if (existing)
            clearTimeout(existing);
        debounceTimers.set(fullPath, setTimeout(() => {
            const type = classifyEvent(fullPath, eventType);
            recordEvent(project, type, fullPath, { source: 'top-level' });
            debounceTimers.delete(fullPath);
        }, 100));
    });
    watchers.push(topWatcher);
    // Recursive: walk subdirs (excluding ignored)
    function watchRecursive(dir, depth = 0) {
        if (depth > 5)
            return; // limit depth
        let entries;
        try {
            entries = fs.readdirSync(dir, { withFileTypes: true });
        }
        catch {
            return;
        }
        for (const entry of entries) {
            if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith('.'))
                continue;
            const full = path.join(dir, entry.name);
            if (entry.isDirectory()) {
                try {
                    const w = fs.watch(full, { persistent: false }, (eventType, filename) => {
                        if (!filename)
                            return;
                        const fp = path.join(full, filename);
                        if (shouldIgnore(fp))
                            return;
                        const existing = debounceTimers.get(fp);
                        if (existing)
                            clearTimeout(existing);
                        debounceTimers.set(fp, setTimeout(() => {
                            const type = classifyEvent(fp, eventType);
                            recordEvent(project, type, fp, { source: 'recursive', depth });
                            debounceTimers.delete(fp);
                        }, 100));
                    });
                    recursiveWatchers.set(full, w);
                    watchRecursive(full, depth + 1);
                }
                catch { }
            }
        }
    }
    watchRecursive(dirPath);
    const info = {
        project,
        dirPath,
        watchers,
        recursiveWatchers,
        debounceTimers,
        eventCount: 0,
        startedAt: new Date().toISOString(),
    };
    activeWatchers.set(project, info);
    return {
        started: true,
        message: `Watcher started for ${project} on ${dirPath} (${watchers.length + recursiveWatchers.size} directories monitored)`,
    };
}
function stopWatcher(project) {
    const w = activeWatchers.get(project);
    if (!w) {
        return { stopped: false, message: 'No active watcher for this project' };
    }
    for (const watcher of w.watchers) {
        try {
            watcher.close();
        }
        catch { }
    }
    for (const watcher of w.recursiveWatchers.values()) {
        try {
            watcher.close();
        }
        catch { }
    }
    for (const timer of w.debounceTimers.values()) {
        clearTimeout(timer);
    }
    activeWatchers.delete(project);
    const db = (0, database_1.getDb)();
    db.prepare('UPDATE watcher_state SET active = 0 WHERE project = ?').run(project);
    return { stopped: true, message: `Watcher stopped for ${project} (${w.eventCount} events captured)` };
}
function getWatcherStatus(project) {
    const db = (0, database_1.getDb)();
    const row = db.prepare(`
    SELECT project, dir_path, active, event_count, last_event_at, started_at
    FROM watcher_state WHERE project = ?
  `).get(project);
    if (!row)
        return null;
    return {
        project: row.project,
        dirPath: row.dir_path,
        active: row.active === 1,
        eventCount: row.event_count,
        lastEventAt: row.last_event_at || '',
        startedAt: row.started_at,
    };
}
function getActiveWatchers() {
    const db = (0, database_1.getDb)();
    const rows = db.prepare(`
    SELECT project, dir_path, active, event_count, last_event_at, started_at
    FROM watcher_state WHERE active = 1
  `).all();
    return rows.map(r => ({
        project: r.project,
        dirPath: r.dir_path,
        active: r.active === 1,
        eventCount: r.event_count,
        lastEventAt: r.last_event_at || '',
        startedAt: r.started_at,
    }));
}
function restoreActiveWatchers() {
    const db = (0, database_1.getDb)();
    const rows = db.prepare(`
    SELECT project, dir_path
    FROM watcher_state WHERE active = 1
  `).all();
    const failed = [];
    let restored = 0;
    for (const row of rows) {
        const project = row.project;
        const dirPath = row.dir_path;
        try {
            if (!fs.existsSync(dirPath)) {
                db.prepare('UPDATE watcher_state SET active = 0 WHERE project = ?').run(project);
                failed.push({ project, dirPath, reason: 'directory missing' });
                continue;
            }
            const result = startWatcher(project, dirPath, { preserveState: true });
            if (result.started || result.message.includes('already running'))
                restored++;
            else
                failed.push({ project, dirPath, reason: result.message });
        }
        catch (e) {
            failed.push({ project, dirPath, reason: e instanceof Error ? e.message : String(e) });
        }
    }
    return { restored, failed };
}
// ─── Event log ────────────────────────────────────────────────────────────
function getLiveEvents(project, limit = 50, eventType) {
    const db = (0, database_1.getDb)();
    let sql = 'SELECT * FROM live_events WHERE project = ?';
    const args = [project];
    if (eventType) {
        sql += ' AND event_type = ?';
        args.push(eventType);
    }
    sql += ' ORDER BY created_at DESC LIMIT ?';
    args.push(limit);
    const rows = db.prepare(sql).all(...args);
    return rows.map(r => ({
        id: Number(r.id),
        project: String(r.project || ''),
        eventType: String(r.event_type || 'file_save'),
        filePath: String(r.file_path || ''),
        metadata: parseMetadata(r.metadata),
        createdAt: String(r.created_at || ''),
    }));
}
function parseMetadata(s) {
    if (typeof s !== 'string' || !s)
        return {};
    try {
        return JSON.parse(s);
    }
    catch {
        return {};
    }
}
// ─── Auto-refresh (7.3 + 7.4) ────────────────────────────────────────────
function logRefresh(trigger) {
    const db = (0, database_1.getDb)();
    const result = db.prepare(`
    INSERT INTO refresh_log (project, trigger_type, success, duration_ms, details)
    VALUES (?, ?, ?, ?, ?)
  `).run(trigger.project, trigger.type, trigger.success ? 1 : 0, trigger.durationMs, trigger.details);
    return { id: Number(result.lastInsertRowid) };
}
function getRefreshHistory(project, limit = 20) {
    const db = (0, database_1.getDb)();
    const rows = db.prepare(`
    SELECT project, trigger_type, success, duration_ms, details, created_at
    FROM refresh_log WHERE project = ? ORDER BY created_at DESC LIMIT ?
  `).all(project, limit);
    return rows.map(r => ({
        type: String(r.trigger_type || 'manual'),
        project: String(r.project || ''),
        success: r.success === 1,
        durationMs: Number(r.duration_ms || 0),
        details: String(r.details || ''),
        createdAt: String(r.created_at || ''),
    }));
}
function checkIfNeedsRefresh(project) {
    const db = (0, database_1.getDb)();
    // Check last scan time
    const lastScan = db.prepare(`
    SELECT MAX(scanned_at) as last FROM scans WHERE project = ?
  `).get(project);
    if (!lastScan.last) {
        return { needsRefresh: true, reason: 'No scan recorded', dataAge: 'never', recommendedAction: 'full-rescan' };
    }
    const ageMs = Date.now() - new Date(lastScan.last + 'Z').getTime();
    const ageDays = ageMs / (1000 * 60 * 60 * 24);
    if (ageDays > 30) {
        return { needsRefresh: true, reason: `Last scan ${Math.round(ageDays)} days ago`, dataAge: `${Math.round(ageDays)}d`, recommendedAction: 'full-rescan' };
    }
    if (ageDays > 3) {
        return { needsRefresh: true, reason: `Last scan ${Math.round(ageDays)} days ago (stale)`, dataAge: `${Math.round(ageDays)}d`, recommendedAction: 're-evaluate' };
    }
    if (ageMs < 2 * 60 * 60 * 1000) {
        return { needsRefresh: false, reason: 'Recently scanned', dataAge: '< 2h', recommendedAction: 'none' };
    }
    return { needsRefresh: false, reason: 'Fresh', dataAge: `${ageDays.toFixed(1)}d`, recommendedAction: 'none' };
}
function performAutoRefresh(project, force = false) {
    const start = Date.now();
    const check = checkIfNeedsRefresh(project);
    if (!check.needsRefresh && !force) {
        const trigger = {
            type: 'scheduled',
            project,
            success: true,
            durationMs: 0,
            details: `Skipped — ${check.reason}`,
            createdAt: new Date().toISOString(),
        };
        logRefresh(trigger);
        return trigger;
    }
    // For now: just log the refresh; full re-scan would call scanCodebase
    const trigger = {
        type: check.recommendedAction === 'full-rescan' ? 'stale' : 'scheduled',
        project,
        success: true,
        durationMs: Date.now() - start,
        details: `Action: ${check.recommendedAction}. Reason: ${check.reason}`,
        createdAt: new Date().toISOString(),
    };
    logRefresh(trigger);
    return trigger;
}
// ─── Cleanup old events ──────────────────────────────────────────────────
function cleanupOldEvents(project, keepCount = 500) {
    const db = (0, database_1.getDb)();
    const result = db.prepare(`
    DELETE FROM live_events
    WHERE project = ? AND id NOT IN (
      SELECT id FROM live_events WHERE project = ? ORDER BY id DESC LIMIT ?
    )
  `).run(project, project, keepCount);
    return { deleted: result.changes };
}
//# sourceMappingURL=live-watcher.js.map