"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 });
exports.GitPlugin = void 0;
const PluginBase_1 = require("./PluginBase");
const EventService_1 = require("../services/EventService");
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
class GitPlugin extends PluginBase_1.PluginBase {
    meta = {
        key: "git",
        name: "Git Plugin",
        description: "Git tracking for agent modifications using .knowhow/.git",
    };
    knowhowGitPath;
    knowhowDir;
    projectRoot;
    projectHasGit = false;
    eventService;
    currentTask = null;
    lintStartCommits = new Map();
    static isListening = false;
    constructor(context = {}) {
        super(context);
        this.projectRoot = process.cwd();
        this.knowhowDir = path.join(this.projectRoot, ".knowhow");
        this.knowhowGitPath = path.join(this.knowhowDir, ".git");
        this.eventService = context.Events || new EventService_1.EventService();
        this.setupEventListeners();
    }
    async call(input) {
        const projectGitStatus = this.getProjectGitStatus();
        return `Git Plugin:

- Current branch: ${this.getCurrentBranch()}
- Agent edit history is tracked separately in .knowhow/.git
- Use git commands with git --git-dir="${this.knowhowGitPath}" to view/revert your changes

PROJECT REPOSITORY STATUS:
${projectGitStatus}

via git status

Note: The files shown above are files that have changed in the user's git repo.  It is likely these files are relevant to the user's request as they've got changes recently.
Your modifications are automatically tracked separately and won't affect the user's git history.`;
    }
    getProjectGitStatus() {
        try {
            const status = (0, child_process_1.execSync)("git status --porcelain", {
                cwd: this.projectRoot,
                stdio: "pipe",
            })
                .toString()
                .trim();
            return status
                ? `Modified files:\n${status}`
                : "- No modified files (working tree clean)";
        }
        catch (error) {
            return `- Error reading project git status: ${error instanceof Error ? error.message : "Unknown error"}`;
        }
    }
    async embed(input) {
        return [];
    }
    async initializeKnowhowRepo() {
        try {
            if (!fs.existsSync(this.knowhowDir)) {
                fs.mkdirSync(this.knowhowDir, { recursive: true });
            }
            if (!fs.existsSync(this.knowhowGitPath)) {
                (0, child_process_1.execSync)("git init", { cwd: this.knowhowDir, stdio: "pipe" });
                const gitignorePath = path.join(this.knowhowDir, ".gitignore");
                fs.writeFileSync(gitignorePath, "# Knowhow agent tracking repository\n");
                try {
                    this.gitCommand("add -A");
                    this.gitCommand('commit -m "Initial commit for agent tracking"');
                }
                catch (error) {
                    this.gitCommand('commit --allow-empty -m "Initial commit for agent tracking"');
                }
            }
            await this.setBranch("main");
        }
        catch (error) {
            this.log(`Failed to initialize .knowhow git repository: ${error}`, "error");
        }
    }
    gitCommand(command, options = { stdio: "pipe" }) {
        try {
            const fullCommand = `git --git-dir="${this.knowhowGitPath}" --work-tree="${this.projectRoot}" ${command}`;
            return (0, child_process_1.execSync)(fullCommand, {
                cwd: this.projectRoot,
                ...options,
            }).toString();
        }
        catch (error) {
            const errorMessage = error.stderr
                ? error.stderr.toString()
                : error.message;
            const newError = new Error(`Git command failed: ${command}\nError: ${errorMessage}`);
            newError.stack = error.stack;
            throw newError;
        }
    }
    safeGitCommand(command, options = { stdio: "pipe" }) {
        try {
            return this.gitCommand(command, options);
        }
        catch (error) {
            this.log(`Safe git command failed: ${command} - ${error}`, "warn");
            return null;
        }
    }
    setupEventListeners() {
        if (!GitPlugin.isListening) {
            this.eventService.on("file:post-edit", async (data) => {
                if (this.isEnabled()) {
                    await this.autoCommit(data);
                }
            });
            this.eventService.on("agent:newTask", async (data) => {
                if (this.isEnabled()) {
                    await this.ensureCleanState(data);
                    await this.handleNewTask(data);
                }
            });
            this.eventService.on("agent:taskComplete", async (data) => {
                if (this.isEnabled()) {
                    await this.handleTaskComplete(data);
                }
            });
            this.eventService.on("linter:started", (data) => {
                if (this.isEnabled()) {
                    const { extension } = data;
                    const currentCommit = this.safeGitCommand("rev-parse --short HEAD")?.trim() || null;
                    if (currentCommit) {
                        this.lintStartCommits.set(extension, currentCommit);
                    }
                }
            });
            this.eventService.on("linter:finished", async (data) => {
                if (this.isEnabled()) {
                    await this.handleLintFinished(data);
                }
            });
            GitPlugin.isListening = true;
        }
    }
    getCurrentBranch() {
        try {
            return this.gitCommand("branch --show-current").trim() || "main";
        }
        catch {
            return "main";
        }
    }
    getRepoHash() {
        let actualRepoHash = null;
        try {
            actualRepoHash = (0, child_process_1.execSync)("git rev-parse --short HEAD", {
                cwd: this.projectRoot,
                stdio: "pipe",
            })
                .toString()
                .trim();
        }
        catch {
            actualRepoHash = null;
        }
        return actualRepoHash;
    }
    async ensureCleanState(taskData) {
        try {
            if (!fs.existsSync(this.knowhowGitPath)) {
                await this.initializeKnowhowRepo();
                return;
            }
            const actualRepoHash = this.getRepoHash();
            this.log(`Current branch is ${this.getCurrentBranch()}`);
            const hasChanges = await this.hasChanges();
            if (hasChanges) {
                try {
                    const message = actualRepoHash
                        ? `sync ${actualRepoHash}`
                        : `sync ${new Date().toISOString()}`;
                    await this.commitAll(message);
                }
                catch (error) {
                    this.log(`Failed to commit uncommitted changes: ${error}`, "error");
                }
            }
            const branchToMerge = this.getCurrentBranch();
            if (this.getCurrentBranch() !== "main") {
                await this.setBranch("main");
                await this.squashMerge(branchToMerge);
            }
            await this.setBranch("main");
        }
        catch (error) {
            this.log(`Failed to ensure clean state: ${error}`, "error");
        }
    }
    async hasChanges() {
        let hasChanges = false;
        try {
            this.gitCommand("diff-index --quiet HEAD --");
        }
        catch {
            hasChanges = true;
        }
        return hasChanges;
    }
    async setBranch(branchName) {
        if (!this.isEnabled())
            return;
        try {
            try {
                this.gitCommand(`rev-parse --verify ${branchName}`);
                this.gitCommand(`checkout ${branchName}`);
            }
            catch {
                this.gitCommand(`checkout -b ${branchName}`);
            }
        }
        catch (error) {
            this.log(`Failed to set branch ${branchName}`, "error");
        }
    }
    async createBranch(branchName) {
        if (!this.isEnabled())
            return;
        try {
            this.gitCommand(`checkout -b ${branchName}`);
        }
        catch (error) {
            this.log(`Failed to create branch ${branchName}`, "error");
        }
    }
    async commit(message, files) {
        if (!this.isEnabled())
            return;
        const hasChanges = await this.hasChanges();
        if (!hasChanges) {
            return;
        }
        if (files && files.length > 0) {
            for (const file of files) {
                try {
                    this.gitCommand(`add "${file}"`);
                }
                catch (error) {
                    this.log(`Failed to add file ${file}: ${error}`, "warn");
                }
            }
        }
        else {
            this.gitCommand("add -A");
        }
        this.ensureValidHead();
        const escapedMessage = message.replace(/\n/g, "\\n");
        this.gitCommand(`commit --allow-empty -m "${escapedMessage}"`);
    }
    async commitAll(message) {
        if (!this.isEnabled())
            return;
        try {
            this.gitCommand("add -A");
            await this.commitWithEvents(message);
        }
        catch (error) {
            this.log(`Failed to commit all changes: ${error}`, "error");
        }
    }
    async commitWithEvents(message, files) {
        try {
            const preCommitResults = await this.eventService.emitBlocking("git:pre-commit", {
                branch: this.getCurrentBranch(),
                message,
                files,
            });
            let enhancedMessage = message;
            if (preCommitResults && preCommitResults.length > 0) {
                const resultMessages = preCommitResults
                    .filter((result) => result && typeof result === "string")
                    .join("\n");
                if (resultMessages) {
                    enhancedMessage += "\n\n" + resultMessages;
                }
            }
            await this.commit(enhancedMessage, files);
            this.eventService.emit("git:post-commit", {
                branch: this.getCurrentBranch(),
                message: enhancedMessage,
                files,
            });
            this.eventService.emit("agent:msg", `
        <Workflow>
        GitPlugin::Commit: ${enhancedMessage} on branch: ${this.getCurrentBranch()}
        You can access your change history via git --git-dir ${this.knowhowGitPath} log or other commands
        This can be used to revert changes, or compare against previous states during a task.
        </Workflow>
        `);
        }
        catch (error) {
            this.log(`Failed to commit with events: ${error}`, "error");
        }
    }
    ensureValidHead() {
        try {
            this.gitCommand("rev-parse HEAD");
        }
        catch {
            try {
                this.gitCommand('commit --allow-empty -m "Initial empty commit"');
            }
            catch (error) {
                this.log(`Could not create initial commit: ${error}`, "warn");
            }
        }
    }
    async autoCommit(data) {
        if (!this.isEnabled())
            return;
        try {
            const { filePath, operation } = data;
            if (!filePath)
                return;
            let message = `Auto-commit: ${operation || "modified"} ${filePath}`;
            if (this.currentTask) {
                message = `[${this.currentTask}] ${message}`;
            }
            await this.commitAll(message);
        }
        catch (error) {
            this.log(`Auto-commit failed: ${error}`, "error");
        }
    }
    async handleNewTask(data) {
        if (!this.isEnabled())
            return;
        try {
            const { taskId, description } = data;
            if (!taskId)
                return;
            const branchName = `task/${taskId}`;
            this.currentTask = taskId;
            await this.createBranch(branchName);
            const hasChanges = await this.hasChanges();
            if (hasChanges) {
                await this.commitWithEvents(`[${taskId}] Start new task: ${description || taskId}`);
            }
            this.log(`Created new task branch: ${branchName}`);
        }
        catch (error) {
            this.log(`Failed to handle new task: ${error}`, "error");
        }
    }
    async handleTaskComplete(data) {
        if (!this.isEnabled())
            return;
        try {
            if (!this.currentTask) {
                this.log("No tasks in progress to complete", "warn");
                return;
            }
            const completedTaskId = this.currentTask;
            const completedBranch = this.getCurrentBranch();
            await this.commitAll("Final commit before merging task");
            await this.setBranch("main");
            const squashMessage = `[${completedTaskId}] Complete task: ${data.answer ? data.answer.substring(0, 100) + "..." : completedTaskId}`;
            await this.squashMerge(completedBranch, squashMessage);
            this.currentTask = null;
        }
        catch (error) {
            this.log(`Failed to handle task completion: ${error}`, "error");
        }
    }
    async handleLintFinished(data) {
        const { extension, success } = data;
        if (!success) {
            this.lintStartCommits.delete(extension);
            return;
        }
        const commitHash = this.lintStartCommits.get(extension);
        this.lintStartCommits.delete(extension);
        if (!commitHash) {
            return;
        }
        try {
            const noteMessage = `[Build Stable] No linting issues found on branch: ${this.getCurrentBranch()}`;
            this.gitCommand(`notes add -f -m "${noteMessage}" ${commitHash}`);
            this.log(`Marked commit ${commitHash} as build stable`);
        }
        catch (error) {
            this.log(`Failed to add git note for commit ${commitHash}: ${error}`, "warn");
        }
    }
    async getGitStatus() {
        if (!this.isEnabled())
            return "Git plugin not enabled";
        try {
            return this.gitCommand("status --porcelain");
        }
        catch (error) {
            return `Error getting git status: ${error}`;
        }
    }
    async getGitLog(count = 10) {
        if (!this.isEnabled())
            return "Git plugin not enabled";
        try {
            return this.gitCommand(`log --oneline -${count}`);
        }
        catch (error) {
            return `Error getting git log: ${error}`;
        }
    }
    async getBranches() {
        if (!this.isEnabled())
            return [];
        try {
            const output = this.gitCommand("branch");
            return output
                .split("\n")
                .map((line) => line.replace(/^\*?\s*/, "").trim())
                .filter((line) => line.length > 0);
        }
        catch (error) {
            this.log(`Error getting branches: ${error}`, "error");
            return [];
        }
    }
    async manualCommit(message, files) {
        await this.commitWithEvents(message, files);
    }
    async manualBranch(branchName) {
        await this.createBranch(branchName);
    }
    async squashMerge(branchName, message = "", squash = true) {
        if (!this.isEnabled())
            return;
        try {
            const mergeCommand = squash
                ? `merge --squash ${branchName}`
                : `merge ${branchName}`;
            this.gitCommand(mergeCommand);
            if (squash) {
                message = message || `Squash merge ${branchName}`;
                this.commitAll(message);
            }
        }
        catch (error) {
            this.log(`Failed to merge ${branchName}: ${error}`, "error");
        }
    }
}
exports.GitPlugin = GitPlugin;
//# sourceMappingURL=GitPlugin.js.map