
================================================================================
FILE: extension.ts
================================================================================

import * as vscode from 'vscode';
import { createStatusBar } from './buddy/buddyUI';
import { triggerBuddyInteraction } from './buddy/buddyController';
import { CodeBuddyViewProvider } from './buddy/buddyView';

export let viewProvider: CodeBuddyViewProvider;

export function activate(context: vscode.ExtensionContext) {
	console.log('CodeMate is now active!');
	const personality = 'pragmatic';

	createStatusBar(context);

	viewProvider = new CodeBuddyViewProvider(context);
	console.log('[CodeMate] View provider created');

	const clickCommand = vscode.commands.registerCommand('codemate.buddyClick', () => {
		const editor = vscode.window.activeTextEditor;
		if (editor) {
			triggerBuddyInteraction({ save: false, document: editor?.document, personality }, viewProvider);
		}
	});
	context.subscriptions.push(clickCommand);

	// Register showBuddy command
	const showCommand = vscode.commands.registerCommand('codemate.showBuddy', () => {
		console.log('[CodeMate] showBuddy command triggered');
		viewProvider.show();
	});
	context.subscriptions.push(showCommand);

	const saveListener = vscode.workspace.onDidSaveTextDocument((document) => {
		triggerBuddyInteraction({
			save: true,
			document,
			personality: personality
		}, viewProvider);
	});
	context.subscriptions.push(saveListener);
}

export function deactivate() { }

================================================================================
FILE: aiPersona\personalities.ts
================================================================================

export const PERSONALITIES = {
    pragmatic: `
Focus on:
- real-world maintainability
- readability
- practical improvements

Ignore:
- theoretical edge cases
- micro-optimizations
`,

    performance: `
Focus on:
- unnecessary loops or computations
- memory inefficiencies
- potential bottlenecks

Ignore:
- naming or style issues
`,

    maintainability: `
Focus on:
- code structure
- separation of concerns
- function size and clarity

Highlight:
- tightly coupled logic
- hard-to-read flows
`,

    security: `
Focus on:
- input validation
- unsafe operations
- potential vulnerabilities

Assume:
- this code may run in production
`,

    product: `
Focus on:
- edge cases affecting users
- error handling
- robustness

Think in terms of:
- user experience
- failure scenarios
`
};

================================================================================
FILE: aiPersona\prompts.ts
================================================================================

export const BASE_SYSTEM_PROMPT = `
You are a senior software engineer reviewing code.

Your task:
- Identify improvements
- Convert each improvement into a TODO task with steps

Output rules:
- Maximum 3 items
- Each item must be one line
- Each item must be actionable
- Each item must include short steps

Formatting rules:
- Each line MUST follow this format:

- <task> | <step 1>; <step 2>; <step 3>

Where:
- <task> = short actionable improvement
- steps = very short, concrete actions

Constraints:
- No explanations
- No reasoning
- No extra text
- No numbering
- Keep everything concise

Good examples:
- Extract validation logic into helper function | locate validation code; move to new function; replace inline calls
- Avoid repeated filtering in loop | move filter outside loop; store result; reuse variable
- Add null checks for external input | identify input sources; add guard clauses; handle fallback case

Bad examples:
- This code could be improved by...
- The function is too complex because...
- Long explanations or paragraphs

Be direct and structured.
`;

================================================================================
FILE: analysis\analyser.ts
================================================================================

import { PERSONALITIES } from '../aiPersona/personalities';
import { BASE_SYSTEM_PROMPT } from '../aiPersona/prompts';
import { AIContext } from '../types/types';
import { buildAIContext } from './contextBuilder';
import * as vscode from 'vscode';

//const API_KEY = 'AIzaSyDgD7hzLXoMf439Fpl08fblzbNQWg1POu8';
const API_KEY = 'AIzaSyDsuWWJZxrgTe2NX48VwAAC5jR87Y4MhHw';

export async function invokeAI(
    document: vscode.TextDocument,
    personality: keyof typeof PERSONALITIES
): Promise<{
    task: string;
    steps: string[];
}[] | string> {

    const context = await buildAIContext(document);

    if (context) {
        const prompt = buildPrompt(context, personality);
        const { GoogleGenAI } = await import("@google/genai");
        const ai = new GoogleGenAI({
            apiKey: API_KEY
        });
        const result = await ai.models.generateContent({
            model: 'gemini-3-flash-preview',
            contents: prompt
        });
        const formatted = result.text
            ? parseAIResponse(result.text)
            : fallbackResponse();
        return formatted;
    }

    return fallbackResponse();
}

export function buildPrompt(context: AIContext, perspective: keyof typeof PERSONALITIES) {
    return `
${BASE_SYSTEM_PROMPT}

${PERSONALITIES[perspective]}

Context:
- File: ${context.file}
- Symbol: ${context.symbolName ?? "unknown"}
- Line: "${context.cursorLine}"
- TODOs: ${context.todos}

Code:
\`\`\`
${context.codeSnippet}
\`\`\`

Review this code.
`;
}

function parseAIResponse(text: string) {
    return text
        .split('\n')
        .map(l => l.trim())
        .filter(l => l.startsWith('- '))
        .slice(0, 3)
        .map(line => {
            const cleaned = line.replace('- ', '');
            const [task, steps] = cleaned.split('|');

            return {
                task: task.trim(),
                steps: steps
                    ? steps.split(';').map(s => s.trim())
                    : []
            };
        });
}

function fallbackResponse(): {
    task: string;
    steps: string[];
}[] {
    return [
        {
            task: "Something changed, but I can't quite place it 🤔",
            steps: []
        }
    ]
}

================================================================================
FILE: analysis\contextBuilder.ts
================================================================================

import * as vscode from 'vscode';
import { findSymbolWithParent } from './symbolUtils';
import { AIContext } from '../types/types';

const MAX_CODE_LENGTH = 2000;

export async function buildAIContext(
    document: vscode.TextDocument,
    position?: vscode.Position
): Promise<AIContext | null> {

    const editor = vscode.window.activeTextEditor;

    // fallback: if no position passed, try active editor
    const cursorPosition =
        position ??
        (editor && editor.document.uri.toString() === document.uri.toString()
            ? editor.selection.active
            : new vscode.Position(0, 0)); // safe fallback

    const fullText = document.getText();

    const cursorLineText = document.lineAt(cursorPosition.line).text;

    const todos = (fullText.match(/TODO/g) || []).length;
    const totalLines = fullText.split('\n').length;

    const symbols = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>(
        'vscode.executeDocumentSymbolProvider',
        document.uri
    );

    let symbolName: string | undefined;
    let symbolType: string | undefined;
    let codeSnippet = "";

    if (symbols) {
        const result = findSymbolWithParent(symbols, cursorPosition);

        if (result) {
            const { symbol } = result;

            symbolName = symbol.name;
            symbolType = vscode.SymbolKind[symbol.kind];

            const symbolText = document.getText(symbol.range);
            codeSnippet = trimCode(symbolText);
        }
    }

    if (!codeSnippet) {
        codeSnippet = trimCode(fullText);
    }

    return {
        file: vscode.workspace.asRelativePath(document.uri),
        symbolName,
        symbolType,
        codeSnippet,
        cursorLine: cursorLineText,
        cursorLineNumber: cursorPosition.line,
        totalLines,
        todos
    };
}

function trimCode(code: string): string {
    if (code.length <= MAX_CODE_LENGTH) return code;

    // Keep beginning + end (better than naive slice)
    const half = Math.floor(MAX_CODE_LENGTH / 2);

    return (
        code.slice(0, half) +
        "\n// ... truncated ...\n" +
        code.slice(-half)
    );
}

================================================================================
FILE: analysis\symbolUtils.ts
================================================================================

import * as vscode from 'vscode';
import { SymbolWithParent } from '../types/types';

export function findSymbolWithParent(
    symbols: vscode.DocumentSymbol[],
    position: vscode.Position,
    parent: vscode.DocumentSymbol | null = null
): SymbolWithParent | undefined {
    for (const symbol of symbols) {
        if (symbol.range.contains(position)) {
            const child = findSymbolWithParent(symbol.children, position, symbol);
            return child || { symbol, parent };
        }
    }
    return undefined;
}

================================================================================
FILE: buddy\buddyController.ts
================================================================================

import * as vscode from 'vscode';
import { BuddyInteractionConfig } from '../types/types';
import { invokeAI } from '../analysis/analyser';
import { updateStatusBar, showMessage } from './buddyUI';
import { CodeBuddyViewProvider } from './buddyView';

export async function triggerBuddyInteraction(
    config: BuddyInteractionConfig,
    viewProvider: CodeBuddyViewProvider
) {
    // 🔹 Update status bar immediately
    updateStatusBar('🐙 CodeBuddy: Reviewing...');

    // 🔹 Show loading state in sidebar
    viewProvider.update(`- Analyzing code | reading context; generating insights`);

    try {
        if (config.save) {
            const message = await invokeAI(config.document, config.personality);

            // 🔹 Update sidebar with structured TODO output
            viewProvider.update(message);

            // 🔹 Minimal toast (don’t duplicate full output)
            showMessage("Review ready ⚡");

        } else {
            // 🔹 Click-triggered interaction (manual)
            const message = await invokeAI(config.document, config.personality);

            viewProvider.update(message);
            showMessage("Review ready ⚡");
        }
    } catch (err) {
        console.error('[CodeMate] Error during AI invocation:', err);

        const fallback = `- Failed to analyze code | check logs; retry operation`;

        viewProvider.update(fallback);
        showMessage("Something went wrong ⚠️");
    } finally {
        // 🔹 Reset status bar
        updateStatusBar('🐙 CodeBuddy: Thinking...');
    }
}

================================================================================
FILE: buddy\buddyUI.ts
================================================================================

import * as vscode from 'vscode';

let statusBarItem: vscode.StatusBarItem;

export function createStatusBar(context: vscode.ExtensionContext) {
    statusBarItem = vscode.window.createStatusBarItem(
        vscode.StatusBarAlignment.Right,
        100
    );

    statusBarItem.text = "🐙 CodeBuddy: Thinking...";
    statusBarItem.command = 'codemate.buddyClick';
    statusBarItem.tooltip = 'Click to interact with CodeBuddy';
    statusBarItem.show();

    context.subscriptions.push(statusBarItem);
}

export function updateStatusBar(text: string) {
    if (statusBarItem) {
        statusBarItem.text = text;
    }
}

export function showMessage(message: string) {
    vscode.window.showInformationMessage(`🐙 CodeBuddy: ${message}`).then(() => {
        updateStatusBar("🐙 CodeBuddy: Thinking...");
    });
}

================================================================================
FILE: buddy\buddyView.ts
================================================================================

import * as vscode from 'vscode';

type TodoItem = {
    task: string;
    steps: string[];
};

export class CodeBuddyViewProvider {
    public static readonly viewType = 'codemate.view';
    public static readonly title = 'CodeBuddy';

    private _panel?: vscode.WebviewPanel;
    private _latestContent: string = "👋 CodeBuddy ready";
    private _context: vscode.ExtensionContext;

    constructor(context: vscode.ExtensionContext) {
        this._context = context;
    }

    public show() {
        if (this._panel) {
            this._panel.reveal();
        } else {
            this._panel = vscode.window.createWebviewPanel(
                CodeBuddyViewProvider.viewType,
                CodeBuddyViewProvider.title,
                vscode.ViewColumn.Beside,
                {
                    enableScripts: true,
                    localResourceRoots: [
                        vscode.Uri.joinPath(this._context.extensionUri, 'media')
                    ]
                }
            );

            this._panel.webview.html = this.getHtml(this._latestContent);

            this._panel.onDidDispose(() => {
                this._panel = undefined;
            });
        }
    }

    update(content: string | TodoItem[]) {
        this._latestContent =
            typeof content === 'string'
                ? content
                : content.map(item =>
                    `- ${item.task} | ${item.steps.join('; ')}`
                ).join('\n');

        // 🔥 FIX: auto-open panel if not created
        if (!this._panel) {
            this.show();
            return;
        }

        this._panel.webview.html = this.getHtml(this._latestContent);
    }

    public getLatestContent(): string {
        return this._latestContent;
    }

    // 🧠 Parse AI response into structured todos
    private parseAIResponse(text: string): TodoItem[] {
        return text
            .split('\n')
            .map(l => l.trim())
            .filter(l => l.startsWith('- '))
            .slice(0, 3)
            .map(line => {
                const cleaned = line.replace('- ', '');
                const [task, steps] = cleaned.split('|');

                return {
                    task: task.trim(),
                    steps: steps
                        ? steps.split(';').map(s => s.trim()).filter(Boolean)
                        : []
                };
            });
    }

    private getHtml(content: string): string {
        const items = this.parseAIResponse(content);

        return `<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline';">
</head>
<body style="
	font-family: system-ui;
	padding: 12px;
	color: #ddd;
	background: #1e1e1e;
">
	<h3>🐙 CodeBuddy</h3>

	${items.length === 0
                ? `<p>No actionable suggestions found.</p>`
                : `<ul style="list-style: none; padding-left: 0;">
			${items.map(item => `
				<li style="margin-bottom: 10px;">
					<label>
						<input type="checkbox" />
						<strong>${item.task}</strong>
					</label>
					${item.steps.length > 0
                        ? `<ul style="margin-top: 5px; padding-left: 20px;">
							${item.steps.map(step => `<li>${step}</li>`).join('')}
						  </ul>`
                        : ''}
				</li>
			`).join('')}
		</ul>`
            }
</body>
</html>`;
    }
}

================================================================================
FILE: test\extension.test.ts
================================================================================

import * as assert from 'assert';

// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';

suite('Extension Test Suite', () => {
	vscode.window.showInformationMessage('Start all tests.');

	test('Sample test', () => {
		assert.strictEqual(-1, [1, 2, 3].indexOf(5));
		assert.strictEqual(-1, [1, 2, 3].indexOf(0));
	});
});


================================================================================
FILE: types\types.ts
================================================================================

import * as vscode from 'vscode';
import { PERSONALITIES } from '../aiPersona/personalities';

export interface BuddyInteractionConfig {
    save: boolean;
    document: vscode.TextDocument;
    personality: keyof typeof PERSONALITIES
}

export interface BuddyResponse {
    message: string;
}

export type SymbolWithParent = {
    symbol: import('vscode').DocumentSymbol;
    parent: import('vscode').DocumentSymbol | null;
};

export interface AIContext {
    file: string;
    symbolName: string | undefined;
    symbolType: string | undefined;
    codeSnippet: string;
    cursorLine: string;
    cursorLineNumber: number;
    totalLines: number;
    todos: number;
}
