/**
 * Profile and learnings query handlers — session scanning, questionnaire,
 * profile generation, and knowledge store management.
 *
 * Ported from get-shit-done/bin/lib/profile-pipeline.cjs, profile-output.cjs,
 * and learnings.cjs.
 *
 * @example
 * ```typescript
 * import { scanSessions, profileQuestionnaire } from './profile.js';
 *
 * await scanSessions([], '/project');
 * // { data: { projects: [...], project_count: 5, session_count: 42 } }
 *
 * await profileQuestionnaire([], '/project');
 * // { data: { mode: 'interactive', questions: [...] } } — same shape as gsd-tools.cjs
 * ```
 */
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
import { join, basename, resolve } from 'node:path';
import { homedir } from 'node:os';
import { createHash, randomBytes } from 'node:crypto';
import { planningPaths } from './helpers.js';
import { GSDError, ErrorClassification } from '../errors.js';
import { buildScanSessionsProjects, getScanSessionsRoot } from './profile-scan-sessions.js';
import { runExtractMessages } from './profile-extract-messages.js';
import { runProfileSample } from './profile-sample.js';
import { PROFILING_QUESTIONS, generateClaudeInstruction, isAmbiguousAnswer, } from './profile-questionnaire-data.js';
// ─── Learnings — ~/.gsd/knowledge/ knowledge store ───────────────────────
const STORE_DIR = join(homedir(), '.gsd', 'knowledge');
function ensureStore() {
    if (!existsSync(STORE_DIR))
        mkdirSync(STORE_DIR, { recursive: true });
}
function learningsWrite(entry) {
    ensureStore();
    const hash = createHash('sha256').update(entry.learning + '\n' + entry.source_project).digest('hex');
    for (const file of readdirSync(STORE_DIR).filter(f => f.endsWith('.json'))) {
        try {
            const r = JSON.parse(readFileSync(join(STORE_DIR, file), 'utf-8'));
            if (r.content_hash === hash)
                return { created: false, id: r.id };
        }
        catch { /* skip */ }
    }
    const id = `${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
    const record = { id, source_project: entry.source_project, date: new Date().toISOString(), context: entry.context ?? '', learning: entry.learning, tags: entry.tags ?? [], content_hash: hash };
    writeFileSync(join(STORE_DIR, `${id}.json`), JSON.stringify(record, null, 2), 'utf-8');
    return { created: true, id };
}
function learningsList() {
    if (!existsSync(STORE_DIR))
        return [];
    const results = [];
    for (const file of readdirSync(STORE_DIR).filter(f => f.endsWith('.json'))) {
        try {
            const record = JSON.parse(readFileSync(join(STORE_DIR, file), 'utf-8'));
            results.push(record);
        }
        catch { /* skip */ }
    }
    results.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
    return results;
}
/**
 * List all entries in the global learnings store (`~/.gsd/knowledge/`).
 *
 * Port of `cmdLearningsList` from learnings.cjs.
 */
export const learningsListHandler = async () => {
    const learnings = learningsList();
    return { data: { learnings, count: learnings.length } };
};
/**
 * Query learnings from the global knowledge store, optionally filtered by tag.
 *
 * Port of `cmdLearningsQuery` from learnings.cjs lines 316-323.
 * Called by gsd-planner agent to inject prior learnings into plan generation.
 *
 * Args: --tag <tag> [--limit N]
 */
export const learningsQuery = async (args) => {
    const tagIdx = args.indexOf('--tag');
    const tag = tagIdx !== -1 ? args[tagIdx + 1] : null;
    const limitIdx = args.indexOf('--limit');
    const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : undefined;
    let results = learningsList();
    if (tag) {
        results = results.filter(r => Array.isArray(r.tags) && r.tags.includes(tag));
    }
    if (limit && limit > 0) {
        results = results.slice(0, limit);
    }
    return { data: { learnings: results, count: results.length, tag } };
};
export const learningsCopy = async (_args, projectDir, workstream) => {
    const paths = planningPaths(projectDir, workstream);
    const learningsPath = join(paths.planning, 'LEARNINGS.md');
    if (!existsSync(learningsPath)) {
        return { data: { copied: false, total: 0, created: 0, skipped: 0, reason: 'No LEARNINGS.md found' } };
    }
    const content = readFileSync(learningsPath, 'utf-8');
    const sourceProject = basename(resolve(projectDir));
    const sections = content.split(/^## /m).slice(1);
    let created = 0;
    let skipped = 0;
    for (const section of sections) {
        const lines = section.trim().split('\n');
        const title = lines[0].trim();
        const body = lines.slice(1).join('\n').trim();
        if (!body)
            continue;
        const tags = title.toLowerCase().split(/\s+/).filter(w => w.length > 2);
        const result = learningsWrite({ source_project: sourceProject, learning: body, context: title, tags });
        if (result.created)
            created++;
        else
            skipped++;
    }
    return { data: { copied: true, total: created + skipped, created, skipped } };
};
/**
 * Prune learnings older than duration (e.g. `90d`). Port of `learningsPrune` from learnings.cjs.
 */
function learningsPruneStore(olderThan) {
    const match = /^(\d+)d$/.exec(olderThan);
    if (!match) {
        throw new Error(`Invalid duration format: "${olderThan}" — expected format like "90d"`);
    }
    const days = parseInt(match[1], 10);
    const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
    if (!existsSync(STORE_DIR))
        return { removed: 0, kept: 0 };
    const files = readdirSync(STORE_DIR).filter(f => f.endsWith('.json'));
    let removed = 0;
    let kept = 0;
    for (const file of files) {
        const filePath = join(STORE_DIR, file);
        let record = null;
        try {
            record = JSON.parse(readFileSync(filePath, 'utf-8'));
        }
        catch {
            continue;
        }
        if (!record?.date)
            continue;
        const recordDate = new Date(record.date);
        if (recordDate < cutoff) {
            unlinkSync(filePath);
            removed++;
        }
        else {
            kept++;
        }
    }
    return { removed, kept };
}
/** Port of `cmdLearningsPrune`. */
export const learningsPrune = async (args) => {
    const olderIdx = args.indexOf('--older-than');
    const olderThan = olderIdx !== -1 ? args[olderIdx + 1] : null;
    if (!olderThan) {
        throw new GSDError('Usage: learnings prune --older-than <duration>', ErrorClassification.Validation);
    }
    try {
        return { data: learningsPruneStore(olderThan) };
    }
    catch (e) {
        const msg = e instanceof Error ? e.message : String(e);
        throw new GSDError(msg, ErrorClassification.Validation);
    }
};
/** Port of `cmdLearningsDelete`. */
export const learningsDelete = async (args) => {
    const id = args[0];
    if (!id) {
        throw new GSDError('Usage: learnings delete <id>', ErrorClassification.Validation);
    }
    if (!/^[a-z0-9]+-[a-f0-9]+$/.test(id)) {
        throw new GSDError(`Invalid learning ID: "${id}"`, ErrorClassification.Validation);
    }
    const filePath = join(STORE_DIR, `${id}.json`);
    if (!existsSync(filePath)) {
        return { data: { id, deleted: false } };
    }
    unlinkSync(filePath);
    return { data: { id, deleted: true } };
};
// ─── extractMessages — session message extraction for profiling ───────────
/**
 * Extract user messages from Claude Code session files for a given project.
 *
 * Port of `cmdExtractMessages` from profile-pipeline.cjs — JSON matches `gsd-tools extract-messages`
 * (`output_file` JSONL + metadata). Uses `--session` (CJS); `--session-id` is accepted as an alias.
 *
 * @param args - args[0]: project name/keyword (required), `--session <id>`, `--limit N`, `--path <dir>`
 */
export const extractMessages = async (args) => {
    const pathIdx = args.indexOf('--path');
    const overridePath = pathIdx !== -1 ? args[pathIdx + 1] : null;
    const sessionIdx = args.indexOf('--session') !== -1 ? args.indexOf('--session') : args.indexOf('--session-id');
    const sessionId = sessionIdx !== -1 ? args[sessionIdx + 1] : null;
    const limitIdx = args.indexOf('--limit');
    const limit = limitIdx !== -1 ? (parseInt(args[limitIdx + 1], 10) || null) : null;
    const projectArg = args[0];
    if (!projectArg || projectArg.startsWith('--')) {
        throw new GSDError('Usage: gsd-tools extract-messages <project> [--session <id>] [--limit N] [--path <dir>]\nRun scan-sessions first to see available projects.', ErrorClassification.Validation);
    }
    const data = await runExtractMessages(projectArg, { sessionId, limit }, overridePath ?? null);
    return { data };
};
// ─── Profile — session scanning and profile generation ────────────────────
export const scanSessions = async (args) => {
    const pathIdx = args.indexOf('--path');
    const overridePath = pathIdx !== -1 ? args[pathIdx + 1] : null;
    const verboseFlag = args.includes('--verbose');
    if (getScanSessionsRoot(overridePath) === null) {
        const searchedPath = overridePath || '~/.claude/projects';
        throw new GSDError(`No Claude Code sessions found at ${searchedPath}.${overridePath ? '' : ' Is Claude Code installed?'}`, ErrorClassification.Validation);
    }
    const projects = buildScanSessionsProjects(overridePath, { verbose: verboseFlag });
    return { data: projects };
};
/**
 * Multi-project session sampling for profiling — port of `cmdProfileSample` (`profile-pipeline.cjs`).
 * JSON matches `gsd-tools profile-sample` (`output_file` JSONL + metadata).
 */
export const profileSample = async (args) => {
    const pathIdx = args.indexOf('--path');
    const overridePath = pathIdx !== -1 ? args[pathIdx + 1] : null;
    const limitIdx = args.indexOf('--limit');
    const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 150;
    const maxPerIdx = args.indexOf('--max-per-project');
    const maxPerProject = maxPerIdx !== -1 ? parseInt(args[maxPerIdx + 1], 10) : null;
    const maxCharsIdx = args.indexOf('--max-chars');
    const maxChars = maxCharsIdx !== -1 ? parseInt(args[maxCharsIdx + 1], 10) : 500;
    const data = await runProfileSample(overridePath ?? null, {
        limit,
        maxPerProject,
        maxChars,
    });
    return { data };
};
/**
 * Profile questionnaire — port of `cmdProfileQuestionnaire` from profile-output.cjs.
 * Interactive: `{ mode: 'interactive', questions }` (options omit `rating`).
 * With `--answers a,b,c,...` (8 comma-separated values, order matches questions): full analysis object (includes volatile `analyzed_at`).
 */
export const profileQuestionnaire = async (args, _projectDir) => {
    const answersIdx = args.indexOf('--answers');
    const answersStr = answersIdx !== -1 ? args[answersIdx + 1] : null;
    if (!answersStr) {
        const questionsOutput = {
            mode: 'interactive',
            questions: PROFILING_QUESTIONS.map((q) => ({
                dimension: q.dimension,
                header: q.header,
                context: q.context,
                question: q.question,
                options: q.options.map((o) => ({ label: o.label, value: o.value })),
            })),
        };
        return { data: questionsOutput };
    }
    const answerValues = answersStr.split(',').map((a) => a.trim());
    if (answerValues.length !== PROFILING_QUESTIONS.length) {
        throw new GSDError(`Expected ${PROFILING_QUESTIONS.length} answers (comma-separated), got ${answerValues.length}`, ErrorClassification.Validation);
    }
    const dimensions = {};
    const analysis = {
        profile_version: '1.0',
        analyzed_at: new Date().toISOString(),
        data_source: 'questionnaire',
        projects_analyzed: [],
        messages_analyzed: 0,
        message_threshold: 'questionnaire',
        sensitive_excluded: [],
        dimensions,
    };
    for (let i = 0; i < PROFILING_QUESTIONS.length; i++) {
        const question = PROFILING_QUESTIONS[i];
        const answerValue = answerValues[i];
        const selectedOption = question.options.find((o) => o.value === answerValue);
        if (!selectedOption) {
            throw new GSDError(`Invalid answer "${answerValue}" for ${question.dimension}. Valid values: ${question.options.map((o) => o.value).join(', ')}`, ErrorClassification.Validation);
        }
        const ambiguous = isAmbiguousAnswer(question.dimension, answerValue);
        dimensions[question.dimension] = {
            rating: selectedOption.rating,
            confidence: ambiguous ? 'LOW' : 'MEDIUM',
            evidence_count: 1,
            cross_project_consistent: null,
            evidence: [
                {
                    signal: 'Self-reported via questionnaire',
                    quote: selectedOption.label,
                    project: 'N/A (questionnaire)',
                },
            ],
            summary: `Developer self-reported as ${selectedOption.rating} for ${question.header.toLowerCase()}.`,
            claude_instruction: generateClaudeInstruction(question.dimension, selectedOption.rating),
        };
    }
    return { data: analysis };
};
//# sourceMappingURL=profile.js.map