"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 });
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
describe('LiveWatcher (Phase 7)', () => {
    let tmpDir;
    let testHome;
    let testProject;
    let originalUserProfile;
    beforeAll(() => {
        originalUserProfile = process.env.USERPROFILE;
        testHome = path.join(os.tmpdir(), 'fivo-cell-live-' + Date.now());
        testProject = 'live-test-proj';
        process.env.USERPROFILE = testHome;
    });
    afterAll(async () => {
        process.env.USERPROFILE = originalUserProfile;
        const { closeDb } = await Promise.resolve().then(() => __importStar(require('../walls/06-memory/database/database')));
        closeDb();
        if (fs.existsSync(testHome)) {
            try {
                fs.rmSync(testHome, { recursive: true, force: true });
            }
            catch { }
        }
    });
    beforeEach(async () => {
        tmpDir = path.join(os.tmpdir(), 'fivo-cell-watch-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8));
        fs.mkdirSync(tmpDir, { recursive: true });
        // Clean DB tables for isolation
        const { getDb } = await Promise.resolve().then(() => __importStar(require('../walls/06-memory/database/database')));
        const db = getDb();
        db.exec('DELETE FROM live_events; DELETE FROM watcher_state; DELETE FROM refresh_log; DELETE FROM session_files; DELETE FROM scans;');
    });
    afterEach(async () => {
        const { stopWatcher } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
        try {
            stopWatcher(testProject);
        }
        catch { }
        if (fs.existsSync(tmpDir)) {
            try {
                fs.rmSync(tmpDir, { recursive: true, force: true });
            }
            catch { }
        }
    });
    describe('startWatcher / stopWatcher', () => {
        it('starts watcher for existing directory', async () => {
            const { startWatcher, getWatcherStatus } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const result = startWatcher(testProject, tmpDir);
            expect(result.started).toBe(true);
            expect(result.message).toContain('Watcher started');
            const status = getWatcherStatus(testProject);
            expect(status).not.toBeNull();
            expect(status?.active).toBe(true);
            expect(status?.dirPath).toBe(tmpDir);
        });
        it('rejects non-existent directory', async () => {
            const { startWatcher } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const result = startWatcher(testProject, path.join(tmpDir, 'does-not-exist'));
            expect(result.started).toBe(false);
            expect(result.message).toContain('does not exist');
        });
        it('refuses double-start', async () => {
            const { startWatcher } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            startWatcher(testProject, tmpDir);
            const second = startWatcher(testProject, tmpDir);
            expect(second.started).toBe(false);
            expect(second.message).toContain('already running');
        });
        it('stops active watcher', async () => {
            const { startWatcher, stopWatcher, getWatcherStatus } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            startWatcher(testProject, tmpDir);
            const stopResult = stopWatcher(testProject);
            expect(stopResult.stopped).toBe(true);
            const status = getWatcherStatus(testProject);
            expect(status?.active).toBe(false);
        });
        it('stop is idempotent', async () => {
            const { stopWatcher } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const result = stopWatcher(testProject);
            expect(result.stopped).toBe(false);
        });
    });
    describe('event recording', () => {
        it('records file_save event when file is modified', async () => {
            const { startWatcher, getLiveEvents } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            // Create file first
            const filePath = path.join(tmpDir, 'test.ts');
            fs.writeFileSync(filePath, 'export const x = 1;');
            startWatcher(testProject, tmpDir);
            // Wait a moment for watcher to settle
            await new Promise(r => setTimeout(r, 200));
            // Modify the file
            fs.writeFileSync(filePath, 'export const x = 2;');
            // Wait for event to be recorded (debounce is 100ms + buffer)
            await new Promise(r => setTimeout(r, 600));
            const events = getLiveEvents(testProject, 50);
            const saveEvents = events.filter(e => e.filePath.endsWith('test.ts'));
            expect(saveEvents.length).toBeGreaterThan(0);
            // Should be file_save or file_create
            expect(['file_save', 'file_create']).toContain(saveEvents[0]?.eventType);
        });
        it('classifies package.json as package_change', async () => {
            const { startWatcher, getLiveEvents } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const pkgPath = path.join(tmpDir, 'package.json');
            fs.writeFileSync(pkgPath, '{}');
            startWatcher(testProject, tmpDir);
            await new Promise(r => setTimeout(r, 200));
            fs.writeFileSync(pkgPath, '{"name":"test"}');
            await new Promise(r => setTimeout(r, 600));
            const events = getLiveEvents(testProject, 50, 'package_change');
            expect(events.length).toBeGreaterThan(0);
            expect(events[0]?.filePath).toContain('package.json');
        });
        it('ignores node_modules and .git', async () => {
            const { startWatcher, getLiveEvents } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            fs.mkdirSync(path.join(tmpDir, 'node_modules'), { recursive: true });
            fs.mkdirSync(path.join(tmpDir, '.git'), { recursive: true });
            startWatcher(testProject, tmpDir);
            await new Promise(r => setTimeout(r, 200));
            fs.writeFileSync(path.join(tmpDir, 'node_modules', 'foo.js'), 'x');
            fs.writeFileSync(path.join(tmpDir, '.git', 'config'), 'x');
            await new Promise(r => setTimeout(r, 600));
            const events = getLiveEvents(testProject, 50);
            const hasIgnored = events.some(e => e.filePath.includes('node_modules') || e.filePath.includes('.git'));
            expect(hasIgnored).toBe(false);
        });
        it('returns events sorted by created_at DESC', async () => {
            const { startWatcher, getLiveEvents } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            fs.writeFileSync(path.join(tmpDir, 'a.ts'), '1');
            fs.writeFileSync(path.join(tmpDir, 'b.ts'), '1');
            startWatcher(testProject, tmpDir);
            await new Promise(r => setTimeout(r, 200));
            fs.writeFileSync(path.join(tmpDir, 'a.ts'), '2');
            await new Promise(r => setTimeout(r, 300));
            fs.writeFileSync(path.join(tmpDir, 'b.ts'), '2');
            await new Promise(r => setTimeout(r, 300));
            const events = getLiveEvents(testProject, 50);
            if (events.length >= 2) {
                const t1 = new Date(events[0].createdAt + 'Z').getTime();
                const t2 = new Date(events[1].createdAt + 'Z').getTime();
                expect(t1).toBeGreaterThanOrEqual(t2);
            }
        });
    });
    describe('session_files integration', () => {
        it('updates session_files touch_count on file save', async () => {
            const { startWatcher } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const { getDb } = await Promise.resolve().then(() => __importStar(require('../walls/06-memory/database/database')));
            const filePath = path.join(tmpDir, 'touched.ts');
            fs.writeFileSync(filePath, '1');
            startWatcher(testProject, tmpDir);
            await new Promise(r => setTimeout(r, 200));
            fs.writeFileSync(filePath, '2');
            await new Promise(r => setTimeout(r, 600));
            const db = getDb();
            const row = db.prepare('SELECT touch_count FROM session_files WHERE project = ? AND file_path = ?').get(testProject, filePath);
            expect(row).toBeDefined();
            expect(row.touch_count).toBeGreaterThanOrEqual(1);
        });
    });
    describe('getActiveWatchers', () => {
        it('lists active watchers', async () => {
            const { startWatcher, getActiveWatchers, stopWatcher } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            startWatcher(testProject, tmpDir);
            const active = getActiveWatchers();
            expect(active.some(a => a.project === testProject && a.active)).toBe(true);
        });
    });
    describe('auto-refresh (7.3 + 7.4)', () => {
        it('returns "no scan recorded" for new project', async () => {
            const { checkIfNeedsRefresh } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const result = checkIfNeedsRefresh(testProject);
            expect(result.needsRefresh).toBe(true);
            expect(result.recommendedAction).toBe('full-rescan');
        });
        it('returns "recently scanned" within 2h of scan', async () => {
            const { checkIfNeedsRefresh } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const { getDb } = await Promise.resolve().then(() => __importStar(require('../walls/06-memory/database/database')));
            const db = getDb();
            db.prepare('INSERT INTO scans (project, scanned_at) VALUES (?, datetime(\'now\'))').run(testProject);
            const result = checkIfNeedsRefresh(testProject);
            expect(result.needsRefresh).toBe(false);
            expect(result.recommendedAction).toBe('none');
        });
        it('returns "re-evaluate" for scan 5 days old', async () => {
            const { checkIfNeedsRefresh } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const { getDb } = await Promise.resolve().then(() => __importStar(require('../walls/06-memory/database/database')));
            const db = getDb();
            db.prepare('INSERT INTO scans (project, scanned_at) VALUES (?, datetime(\'now\', \'-5 days\'))').run(testProject);
            const result = checkIfNeedsRefresh(testProject);
            expect(result.needsRefresh).toBe(true);
            expect(result.recommendedAction).toBe('re-evaluate');
        });
        it('returns "full-rescan" for scan 31 days old', async () => {
            const { checkIfNeedsRefresh } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const { getDb } = await Promise.resolve().then(() => __importStar(require('../walls/06-memory/database/database')));
            const db = getDb();
            db.prepare('INSERT INTO scans (project, scanned_at) VALUES (?, datetime(\'now\', \'-31 days\'))').run(testProject);
            const result = checkIfNeedsRefresh(testProject);
            expect(result.needsRefresh).toBe(true);
            expect(result.recommendedAction).toBe('full-rescan');
        });
        it('performAutoRefresh logs to refresh_log table', async () => {
            const { performAutoRefresh, getRefreshHistory } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            performAutoRefresh(testProject, false);
            const history = getRefreshHistory(testProject, 5);
            expect(history.length).toBeGreaterThan(0);
            expect(history[0]?.project).toBe(testProject);
        });
    });
    describe('cleanupOldEvents', () => {
        it('keeps only the most recent N events', async () => {
            const { startWatcher, getLiveEvents, cleanupOldEvents } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            // Create 10 files to generate events
            for (let i = 0; i < 10; i++) {
                fs.writeFileSync(path.join(tmpDir, `f${i}.ts`), 'x');
            }
            startWatcher(testProject, tmpDir);
            await new Promise(r => setTimeout(r, 200));
            // Trigger saves
            for (let i = 0; i < 10; i++) {
                fs.writeFileSync(path.join(tmpDir, `f${i}.ts`), `x${i}`);
            }
            await new Promise(r => setTimeout(r, 800));
            const beforeCount = getLiveEvents(testProject, 100).length;
            expect(beforeCount).toBeGreaterThan(0);
            const result = cleanupOldEvents(testProject, 3);
            expect(result.deleted).toBeGreaterThanOrEqual(0);
            const afterCount = getLiveEvents(testProject, 100).length;
            expect(afterCount).toBeLessThanOrEqual(3);
        });
    });
    describe('CLI args parser (path with spaces)', () => {
        it('preserves quoted paths with spaces', () => {
            // Inline test of the parser logic (mirrors src/cli.ts parseArgs)
            function parseArgs(argv) {
                const out = [];
                let buf = '';
                let inQuote = false;
                for (const a of argv) {
                    if (inQuote) {
                        buf += ' ' + a;
                        if (a.endsWith('"')) {
                            out.push(buf.slice(0, -1));
                            buf = '';
                            inQuote = false;
                        }
                    }
                    else if (a.startsWith('"') && !a.endsWith('"')) {
                        buf = a.slice(1);
                        inQuote = true;
                    }
                    else if (a.startsWith('"') && a.endsWith('"') && a.length > 1) {
                        out.push(a.slice(1, -1));
                    }
                    else {
                        out.push(a);
                    }
                }
                if (buf)
                    out.push(buf);
                return out;
            }
            // The bug: space inside quoted path used to split across two args
            const argv1 = ['watch', 'daemon', 'Fivo-Cell', '"C:\\Users\\ss\\Downloads\\Pulse Project\\Fivo-Cell"'];
            expect(parseArgs(argv1)).toEqual(['watch', 'daemon', 'Fivo-Cell', 'C:\\Users\\ss\\Downloads\\Pulse Project\\Fivo-Cell']);
            const argv2 = ['start', '--flag', 'value'];
            expect(parseArgs(argv2)).toEqual(['start', '--flag', 'value']);
            // unquoted paths work too
            const argv3 = ['watch', 'daemon', 'Fivo-Cell', 'C:/nospaces'];
            expect(parseArgs(argv3)).toEqual(['watch', 'daemon', 'Fivo-Cell', 'C:/nospaces']);
        });
    });
    describe('restoreActiveWatchers', () => {
        it('restores active watchers from DB', async () => {
            const { startWatcher, stopWatcher, restoreActiveWatchers } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            // Start then stop — this sets active=1 then active=0 in DB
            startWatcher(testProject, tmpDir);
            stopWatcher(testProject);
            // Manually re-activate the DB row to simulate a pre-reboot state
            const { getDb } = await Promise.resolve().then(() => __importStar(require('../walls/06-memory/database/database')));
            const db = getDb();
            db.prepare('UPDATE watcher_state SET active = 1 WHERE project = ?').run(testProject);
            const restored = restoreActiveWatchers();
            expect(restored.restored).toBe(1);
            expect(restored.failed).toHaveLength(0);
        });
        it('marks stale entries when directory no longer exists', async () => {
            const { restoreActiveWatchers } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const { getDb } = await Promise.resolve().then(() => __importStar(require('../walls/06-memory/database/database')));
            const db = getDb();
            db.prepare(`
        INSERT INTO watcher_state (project, dir_path, active, started_at)
        VALUES (?, ?, 1, datetime('now'))
      `).run('ghost-project', '/tmp/does-not-exist-' + Date.now());
            const restored = restoreActiveWatchers();
            expect(restored.failed.length).toBeGreaterThanOrEqual(1);
            const ghost = restored.failed.find(f => f.project === 'ghost-project');
            expect(ghost).toBeDefined();
            expect(ghost.reason).toContain('directory missing');
            // DB row should be deactivated
            const row = db.prepare('SELECT active FROM watcher_state WHERE project = ?').get('ghost-project');
            expect(row.active).toBe(0);
        });
        it('returns 0 restored when no active watchers in DB', async () => {
            const { restoreActiveWatchers } = await Promise.resolve().then(() => __importStar(require('../walls/07-runtime/watcher/live-watcher')));
            const { getDb } = await Promise.resolve().then(() => __importStar(require('../walls/06-memory/database/database')));
            const db = getDb();
            db.prepare('UPDATE watcher_state SET active = 0');
            const restored = restoreActiveWatchers();
            expect(restored.restored).toBe(0);
            expect(restored.failed).toHaveLength(0);
        });
    });
});
//# sourceMappingURL=live-watcher.test.js.map