/**
 * Insights + Preferences API routes
 *
 * Per-user AI insights and home screen preferences.
 */
import { join } from 'path';
import { homedir } from 'os';
import { existsSync, readdirSync, rmSync } from 'fs';
import * as access from '../../services/access-control.js';
import { findPersonRefForUser } from '../../services/user-person-bridge.js';
import { log } from '../../utils/log.js';
function getUserId(req) {
    return req.user?.id ?? 0;
}
function getAccessibleRepoNames(req, wm, authService) {
    const user = req.user;
    // Open mode (no user) keeps the legacy filter that excludes personal folders.
    if (!user)
        return wm.getAllRepoConfigs().filter(r => !r.personal).map(r => r.name);
    // Authed: delegate to access-control so personal-folder restrictions and
    // owner-on-shared-folder semantics match the HTTP read endpoints.
    return access.getAccessibleRepos(user, wm, authService).map(r => r.name);
}
export async function registerInsightsApiRoutes(fastify, workspaceManager, authService, workspacePath, wsHub) {
    // GET /api/home — aggregated data for the homepage: open tasks + today's
    // meetings across every folder the caller has access to. One round trip
    // replaces the N per-folder entity fetches the home page used to scan.
    fastify.get('/api/home', async (req, reply) => {
        const repoNames = getAccessibleRepoNames(req, workspaceManager, authService);
        const today = new Date();
        const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
        const openTasks = [];
        const todaysMeetings = [];
        for (const name of repoNames) {
            let graph;
            try {
                graph = workspaceManager.getGraph(name);
            }
            catch {
                continue;
            }
            for (const entity of graph.search({ entityType: 'task' })) {
                const d = entity.data;
                const status = typeof d.status === 'string' ? d.status : 'open';
                if (status !== 'open' && status !== 'in-progress')
                    continue;
                openTasks.push({
                    id: entity.id,
                    title: d.name ?? entity.id,
                    repo: name,
                    status,
                    priority: d.priority,
                    assignee: d.assignee,
                    dueDate: d.date,
                });
            }
            for (const entity of graph.search({ entityType: 'event' })) {
                const d = entity.data;
                const date = typeof d.date === 'string' ? d.date : '';
                if (!date || date.slice(0, 10) !== todayStr)
                    continue;
                todaysMeetings.push({
                    id: entity.id,
                    title: d.name ?? entity.id,
                    repo: name,
                    date,
                    attendees: d.attendees,
                });
            }
        }
        todaysMeetings.sort((a, b) => a.date.localeCompare(b.date));
        // Hand the caller the wikilink target ID of the current user's person
        // entity (the bridge), so the home screen can scope "My Tasks" without
        // re-deriving a slug from displayName. Null when the user has no
        // linked person record — OR when the linked entity lives in a repo
        // the caller can't read (would leak repo membership otherwise).
        const user = req.user;
        const myPersonRef = user ? findPersonRefForUser(workspaceManager, user.handle) : null;
        const currentUserPersonId = myPersonRef && (!user || access.hasRepoAccess(user, myPersonRef.repo, workspaceManager, authService))
            ? myPersonRef.entityId
            : null;
        return reply.send({ openTasks, todaysMeetings, currentUserPersonId });
    });
    // GET /api/graph/health — graph health report (lint, orphans, stale, broken links)
    fastify.get('/api/graph/health', async (req, reply) => {
        try {
            const { analyzeGraphHealth } = await import('../../services/graph-maintenance.js');
            const report = await analyzeGraphHealth(workspaceManager);
            return reply.send(report);
        }
        catch (err) {
            return reply.status(500).send({ error: err.message ?? 'Health check failed' });
        }
    });
    // GET /api/preferences — get user preferences
    fastify.get('/api/preferences', async (req, reply) => {
        const userId = getUserId(req);
        return reply.send({
            accent_color: authService.getAccentColor(userId),
            onboarded: authService.getOnboarded(userId),
        });
    });
    // PUT /api/preferences — update user preferences
    fastify.put('/api/preferences', async (req, reply) => {
        const userId = getUserId(req);
        if (req.body?.accent_color !== undefined) {
            authService.setAccentColor(userId, req.body.accent_color);
        }
        if (req.body?.onboarded !== undefined) {
            authService.setOnboarded(userId, req.body.onboarded);
        }
        return reply.send({ ok: true });
    });
    // ── Generic per-user settings (today: Deepgram key, calendar, default folder — all meeting-capture) ──
    // Storage is generic JSON; meeting-capture UI is the only current consumer and is itself feature-gated.
    // If future features add their own keys, gate at the key level in their own handlers, not here.
    //
    // Phase 8: `authService.setUserSettings()` partitions keys by Key Registry
    // classification — `user-secret` keys land in `settings_ciphertext`, the
    // rest stay in `settings_json`. `getUserSettings()` decrypts and merges
    // both halves so this endpoint's wire contract is unchanged.
    // GET /api/preferences/settings — get all per-user settings
    fastify.get('/api/preferences/settings', async (req, reply) => {
        const userId = getUserId(req);
        return reply.send(authService.getUserSettings(userId));
    });
    // PATCH /api/preferences/settings — merge partial settings update
    fastify.patch('/api/preferences/settings', async (req, reply) => {
        const userId = getUserId(req);
        if (!req.body || typeof req.body !== 'object') {
            return reply.status(400).send({ error: 'Body must be a JSON object' });
        }
        // setUserSettings is registry-aware: it classifies each merged key via
        // getKeyDef and routes user-secret entries through the encrypted column.
        authService.setUserSettings(userId, req.body);
        return reply.send(authService.getUserSettings(userId));
    });
    // POST /api/reset — full per-user fresh-start. Wipes sessions + memory
    // AND resets DB-backed agent state: onboarded flag and meeting-capture
    // settings. Preserves the user account, accent color, and API tokens.
    // Use this for QA flows where a tester needs to re-experience onboarding
    // from scratch.
    //
    // (Pre-C5 there was also a `/api/clear` route for the narrower "wipe
    // sessions/memory only" case. It was removed when `studiograph clear`
    // was folded into `studiograph reset` — see cli-owner-audit §3.3.)
    fastify.post('/api/reset', async (req, reply) => {
        const userId = getUserId(req);
        if (!userId)
            return reply.status(401).send({ error: 'Authentication required' });
        try {
            const baseDir = join(homedir(), '.studiograph');
            const sessions = clearUserDir(join(baseDir, 'sessions', String(userId)), '.json');
            const memory = clearUserDir(join(baseDir, 'memory', String(userId)), '.md');
            authService.resetUserState(userId);
            // See note in /api/clear above — broadcast so any open tabs drop
            // their localStorage transcript and re-fetch the user's now-reset
            // state (onboarded flag, etc.).
            wsHub?.broadcastToUser?.(String(userId), {
                type: 'state_cleared',
                target: 'all',
                timestamp: new Date().toISOString(),
            });
            return reply.send({ ok: true, cleared: { sessions, memory }, dbStateReset: true });
        }
        catch (err) {
            log.error('[reset] failed:', err?.stack || err?.message || err);
            return reply.status(500).send({ error: err?.message || 'Reset failed' });
        }
    });
}
function clearUserDir(dirPath, ext) {
    if (!existsSync(dirPath))
        return 0;
    const files = readdirSync(dirPath).filter(f => f.endsWith(ext));
    for (const f of files)
        rmSync(join(dirPath, f));
    return files.length;
}
//# sourceMappingURL=insights-api.js.map