import * as vscode from 'vscode';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';

// ─── 브레인 프로필 인터페이스 ───
export interface BrainProfile {
    id: string;
    name: string;
    localBrainPath: string;
    secondBrainRepo?: string;
    description?: string;
}

// ─── 에이전트 설정 인터페이스 (통합 버전) ───
export interface IAgentConfig {
    ollamaUrl: string;
    defaultModel: string;
    maxTreeFiles: number;
    timeout: number;
    localBrainPath: string;
    secondBrainRepo: string;
    brainProfiles: BrainProfile[];
    activeBrainId: string;
    maxContextSize: number;
    maxAutoSteps: number;
    dryRun: boolean;
    multiAgentEnabled: boolean;
    memoryEnabled: boolean;
    memoryShortTermMessages: number;
    memoryMediumTermSessions: number;
    memoryLongTermFiles: number;
    // ─── 컨텍스트 한계 관리 ───
    contextLength: number;
    maxOutputTokens: number;
    contextSafetyMargin: number;
    contextOverflowPolicy: 'stopAtLimit' | 'truncateMiddle' | 'rollingWindow';
    autoCompactHistory: boolean;
    /** 작은 모델(≤4B) 감지 시 예산 계산에 쓸 유효 context window 상한. 0 = 비활성화. */
    smallModelContextCap: number;
    // ─── 응답 복구 (Thought Quarantine / Auto-Continuation) ───
    /** 답변이 출력 토큰 한계에 걸리면 사용자 개입 없이 내부적으로 이어서 생성. */
    autoContinueOnOutputLimit: boolean;
    /** 자동 이어쓰기 최대 횟수 (무한 반복 방지). 0 = 비활성화. */
    maxAutoContinuations: number;
    /** 모델이 내부 사고만 출력하고 답변이 없으면 "최종 답변만" 지시로 1회 재생성. */
    finalOnlyRetryOnThoughtLeak: boolean;
    // ─── Hybrid Semantic Search ───
    /**
     * Embedding model name as registered in LM Studio / Ollama. Empty disables
     * semantic search and the retriever falls back to TF-IDF only. The user
     * must load this model in the engine before enabling it here.
     */
    embeddingModel: string;
    /**
     * Blend between TF-IDF (sparse) and embedding cosine (dense) scoring.
     * 0 = TF-IDF only (status quo), 1 = embedding only.
     * Default 0.5 = equal weight, a reasonable starting point.
     */
    embeddingBlendAlpha: number;
    /**
     * Global Knowledge Mix weight (0–100). Controls how much the assistant leans on
     * Second Brain evidence vs. model general knowledge when answering.
     *   0   → Second Brain disabled; model knowledge only.
     *   50  → Balanced (default).
     *   100 → Second Brain is primary evidence; model knowledge only fills gaps.
     * Per-agent overrides live in AgentKnowledgeEntry.secondBrainWeight and win.
     */
    knowledgeMixSecondBrainWeight: number;
    /**
     * [Self-Reflection] Researcher와 Writer 사이에 메타인지 단계(Reflector)를 삽입할지 여부.
     * true(기본): Reflector가 plan/research를 비판적으로 검토한 critique을 Writer에 주입.
     * false: 기존 3단계(Planner→Researcher→Writer) 그대로 — 1 LLM 호출 절약 (저성능 모델/저지연 우선 시).
     */
    enableReflection: boolean;
}

// ─── 경로 정규화 유틸리티 ───
function normalizePath(p: string): string {
    if (!p) return p;
    if (p.startsWith('~/')) {
        return path.join(os.homedir(), p.substring(2));
    }
    return p.trim();
}

function toBrainProfile(raw: Partial<BrainProfile> | undefined, fallbackIndex: number): BrainProfile | null {
    if (!raw) return null;
    const localBrainPath = normalizePath(raw.localBrainPath || '');
    if (!localBrainPath) return null;
    return {
        id: (raw.id || `brain-${fallbackIndex + 1}`).trim(),
        name: (raw.name || path.basename(localBrainPath) || `Brain ${fallbackIndex + 1}`).trim(),
        localBrainPath,
        secondBrainRepo: (raw.secondBrainRepo || '').trim(),
        description: (raw.description || '').trim()
    };
}

// ─── VS Code 설정에서 읽어오는 값 (통합 구현) ───
export function getConfig(): IAgentConfig {
    const cfg = vscode.workspace.getConfiguration('g1nation');
    
    // 브레인 프로필 로직 (utils.ts에서 이관)
    const legacyBrainPath = cfg.get<string>('localBrainPath', '');
    const legacyBrainRepo = cfg.get<string>('secondBrainRepo', '');
    const configuredProfiles = cfg.get<Partial<BrainProfile>[]>('brainProfiles', []);
    const profiles = configuredProfiles
        .map((profile, index) => toBrainProfile(profile, index))
        .filter((profile): profile is BrainProfile => !!profile);

    if (profiles.length === 0) {
        const fallbackPath = normalizePath(legacyBrainPath) || path.join(os.homedir(), '.g1nation-brain');
        profiles.push({
            id: 'default-brain',
            name: 'Local Brain',
            localBrainPath: fallbackPath,
            secondBrainRepo: legacyBrainRepo.trim(),
            description: legacyBrainPath
                ? 'Migrated from your existing localBrainPath setting'
                : 'Auto-created local knowledge folder.'
        });
    }

    const activeBrainId = cfg.get<string>('activeBrainId', profiles[0].id) || profiles[0].id;
    const activeBrain = profiles.find((profile) => profile.id === activeBrainId) || profiles[0];

    return {
        ollamaUrl: cfg.get<string>('ollamaUrl', 'http://127.0.0.1:11434') || 'http://127.0.0.1:11434',
        defaultModel: cfg.get<string>('defaultModel', 'gemma4:e2b') || 'gemma4:e2b',
        maxTreeFiles: 200,
        timeout: cfg.get<number>('requestTimeout', 300) * 1000,
        localBrainPath: activeBrain.localBrainPath,
        secondBrainRepo: activeBrain.secondBrainRepo || '',
        brainProfiles: profiles,
        activeBrainId: activeBrain.id,
        maxContextSize: cfg.get<number>('maxContextSize', 12000),
        maxAutoSteps: cfg.get<number>('maxAutoSteps', 50),
        dryRun: cfg.get<boolean>('dryRun', false),
        multiAgentEnabled: cfg.get<boolean>('multiAgentEnabled', false),
        memoryEnabled: cfg.get<boolean>('memoryEnabled', true),
        memoryShortTermMessages: Math.max(0, cfg.get<number>('memoryShortTermMessages', 8)),
        memoryMediumTermSessions: Math.max(0, cfg.get<number>('memoryMediumTermSessions', 5)),
        memoryLongTermFiles: Math.max(0, cfg.get<number>('memoryLongTermFiles', 6)),
        contextLength: Math.max(2048, cfg.get<number>('contextLength', 32768)),
        maxOutputTokens: Math.max(256, cfg.get<number>('maxOutputTokens', 4096)),
        contextSafetyMargin: Math.max(0, cfg.get<number>('contextSafetyMargin', 2048)),
        contextOverflowPolicy: ((): IAgentConfig['contextOverflowPolicy'] => {
            const v = cfg.get<string>('contextOverflowPolicy', 'stopAtLimit');
            return v === 'truncateMiddle' || v === 'rollingWindow' ? v : 'stopAtLimit';
        })(),
        autoCompactHistory: cfg.get<boolean>('autoCompactHistory', true),
        smallModelContextCap: Math.max(0, cfg.get<number>('smallModelContextCap', 0)),
        autoContinueOnOutputLimit: cfg.get<boolean>('autoContinueOnOutputLimit', true),
        maxAutoContinuations: Math.max(0, Math.min(10, cfg.get<number>('maxAutoContinuations', 4))),
        finalOnlyRetryOnThoughtLeak: cfg.get<boolean>('finalOnlyRetryOnThoughtLeak', true),
        embeddingModel: (cfg.get<string>('embeddingModel', '') || '').trim(),
        embeddingBlendAlpha: Math.max(0, Math.min(1, cfg.get<number>('embeddingBlendAlpha', 0.5))),
        knowledgeMixSecondBrainWeight: Math.max(0, Math.min(100, Math.round(
            cfg.get<number>('knowledgeMix.secondBrainWeight', 50)
        ))),
        enableReflection: cfg.get<boolean>('enableReflection', true),
    };
}

/**
 * Config Validator: Validates the current configuration.
 */
export function validateConfig(): { valid: boolean; errors: string[] } {
    const config = getConfig();
    const errors: string[] = [];

    // 1. Ollama URL Validation
    try {
        new URL(config.ollamaUrl);
    } catch (e) {
        errors.push(`Invalid Ollama URL: ${config.ollamaUrl}`);
    }

    // 2. Brain Path Validation
    if (config.localBrainPath && !fs.existsSync(config.localBrainPath)) {
        errors.push(`Brain path does not exist: ${config.localBrainPath}`);
    }

    return {
        valid: errors.length === 0,
        errors
    };
}

// ─── 보안 정책 (Security Policy) ───
export const SECURITY_POLICY = {
    allowedCommandPrefixes: [
        'npm', 'yarn', 'pnpm', 'npx', 'node', 'ts-node', 'git', 'python', 'python3', 'pip', 'pip3',
        'docker', 'docker-compose', 'ls', 'dir', 'cat', 'type', 'echo', 'print', 'cargo', 'go', 'rustc',
        'java', 'javac', 'mvn', 'gradle', 'flutter', 'dart', 'pub', 'webpack', 'vite', 'esbuild', 'parcel',
        'jest', 'mocha', 'vitest', 'cypress', 'tsc', 'vue-tsc',
    ],
    forbiddenCommands: [
        'rm -rf', 'rm-rf', 'del /f', 'format', 'mkfs', 'dd if=', ':(){ :|:& };:',
        'wget http', 'curl http', 'sudo', 'chmod 777', 'chown root',
    ],
    sensitiveFilePatterns: [
        '.env', '.env.*', 'id_rsa', 'id_ed25519', '.gitconfig', '.npmrc', '.pypirc',
        'credentials.json', 'service-account.json',
    ],
    maxFileSize: 10 * 1024 * 1024,
    maxContextFiles: 200,
};


export const EXCLUDED_DIRS = new Set([
    'node_modules', '.git', '.vscode', 'out', 'dist', 'build',
    '.next', '.cache', '__pycache__', '.DS_Store', 'coverage',
    '.turbo', '.nuxt', '.output', 'vendor', 'target', '.astra'
]);
