"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 });
// backend/src/routes/git.ts
const express_1 = require("express");
const path = __importStar(require("path"));
const simple_git_1 = require("simple-git");
const config_1 = require("../config");
const router = (0, express_1.Router)();
// Helper: validate caller has edit access to project
function canEdit(project, username) {
    if ((0, config_1.isAdminUser)(username))
        return true;
    if ((0, config_1.isProjectOwner)(project, username))
        return true;
    return project.shares?.some((s) => s.username === username && s.permission === 'edit') ?? false;
}
// GET /api/projects/:id/git/status
router.get('/:id/git/status', async (req, res) => {
    const project = (0, config_1.getProject)(req.params.id);
    if (!project) {
        res.status(404).json({ error: 'Project not found' });
        return;
    }
    if (!canEdit(project, req.user?.username)) {
        res.status(403).json({ error: 'Forbidden' });
        return;
    }
    try {
        const git = (0, simple_git_1.simpleGit)(project.folderPath);
        const isRepo = await git.checkIsRepo();
        if (!isRepo) {
            res.json({ isRepo: false });
            return;
        }
        const status = await git.status();
        res.json({
            isRepo: true,
            branch: status.current,
            staged: status.staged,
            modified: status.modified,
            untracked: status.not_added,
            deleted: status.deleted,
            ahead: status.ahead,
            behind: status.behind,
        });
    }
    catch (err) {
        res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
    }
});
// GET /api/projects/:id/git/diff?file=<path>
router.get('/:id/git/diff', async (req, res) => {
    const project = (0, config_1.getProject)(req.params.id);
    if (!project) {
        res.status(404).json({ error: 'Project not found' });
        return;
    }
    if (!canEdit(project, req.user?.username)) {
        res.status(403).json({ error: 'Forbidden' });
        return;
    }
    const fileParam = typeof req.query.file === 'string' ? req.query.file : undefined;
    // Reject option-style values to prevent `--no-index /etc/passwd ...` injection
    // and any path that escapes the project root via traversal.
    if (fileParam !== undefined) {
        if (fileParam.startsWith('-')) {
            res.status(400).json({ error: 'Invalid file path' });
            return;
        }
        const resolved = path.resolve(project.folderPath, fileParam);
        if (!resolved.startsWith(project.folderPath + path.sep) && resolved !== project.folderPath) {
            res.status(400).json({ error: 'Invalid file path' });
            return;
        }
    }
    try {
        const git = (0, simple_git_1.simpleGit)(project.folderPath);
        // Pass `--` so even an unforeseen option-looking value is treated as a path.
        const diff = fileParam ? await git.diff(['--', fileParam]) : await git.diff();
        res.json({ diff });
    }
    catch (err) {
        res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
    }
});
// POST /api/projects/:id/git/add   body: { files: string[] }
router.post('/:id/git/add', async (req, res) => {
    const project = (0, config_1.getProject)(req.params.id);
    if (!project) {
        res.status(404).json({ error: 'Project not found' });
        return;
    }
    if (!canEdit(project, req.user?.username)) {
        res.status(403).json({ error: 'Forbidden' });
        return;
    }
    const { files } = req.body;
    if (!Array.isArray(files) || files.length === 0 || !files.every((f) => typeof f === 'string')) {
        res.status(400).json({ error: 'files must be a non-empty string array' });
        return;
    }
    // Validate all paths are within the project directory (prevent path traversal)
    for (const f of files) {
        const resolved = path.resolve(project.folderPath, f);
        if (!resolved.startsWith(project.folderPath + path.sep) && resolved !== project.folderPath) {
            res.status(400).json({ error: `Invalid file path: ${f}` });
            return;
        }
    }
    try {
        const git = (0, simple_git_1.simpleGit)(project.folderPath);
        await git.add(files);
        res.json({ ok: true });
    }
    catch (err) {
        res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
    }
});
// POST /api/projects/:id/git/commit   body: { message: string }
router.post('/:id/git/commit', async (req, res) => {
    const project = (0, config_1.getProject)(req.params.id);
    if (!project) {
        res.status(404).json({ error: 'Project not found' });
        return;
    }
    if (!canEdit(project, req.user?.username)) {
        res.status(403).json({ error: 'Forbidden' });
        return;
    }
    const { message } = req.body;
    if (typeof message !== 'string' || !message.trim()) {
        res.status(400).json({ error: 'commit message required' });
        return;
    }
    try {
        const git = (0, simple_git_1.simpleGit)(project.folderPath);
        const result = await git.commit(message.trim());
        res.json({ ok: true, commit: result.commit });
    }
    catch (err) {
        res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
    }
});
// GET /api/projects/:id/git/log?limit=50&skip=0
router.get('/:id/git/log', async (req, res) => {
    const project = (0, config_1.getProject)(req.params.id);
    if (!project) {
        res.status(404).json({ error: 'Project not found' });
        return;
    }
    if (!canEdit(project, req.user?.username)) {
        res.status(403).json({ error: 'Forbidden' });
        return;
    }
    const limit = Math.min(parseInt(req.query.limit) || 50, 200);
    const skip = parseInt(req.query.skip) || 0;
    try {
        const git = (0, simple_git_1.simpleGit)(project.folderPath);
        const isRepo = await git.checkIsRepo();
        if (!isRepo) {
            res.json({ commits: [] });
            return;
        }
        const log = await git.log({
            maxCount: limit,
            '--skip': skip,
            '--all': null,
            '--topo-order': null,
        });
        // Get all branches for decoration
        const branchSummary = await git.branch(['-a']);
        const branchMap = {};
        for (const [name, info] of Object.entries(branchSummary.branches)) {
            const hash = info.commit;
            if (!branchMap[hash])
                branchMap[hash] = [];
            branchMap[hash].push(name);
        }
        const commits = log.all.map((c) => ({
            hash: c.hash,
            hashShort: c.hash.slice(0, 7),
            message: c.message,
            author: c.author_name,
            date: c.date,
            parents: [],
            branches: branchMap[c.hash] ?? [],
        }));
        // Get parent info via raw log format
        const rawLog = await git.raw([
            'log', '--all', '--topo-order',
            `--max-count=${limit}`, `--skip=${skip}`,
            '--format=%H %P',
        ]);
        const parentMap = {};
        for (const line of rawLog.trim().split('\n')) {
            if (!line)
                continue;
            const parts = line.split(' ');
            parentMap[parts[0]] = parts.slice(1);
        }
        for (const c of commits) {
            c.parents = (parentMap[c.hash] ?? []).map(h => h.slice(0, 7));
        }
        res.json({
            commits,
            total: log.total,
        });
    }
    catch (err) {
        res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
    }
});
exports.default = router;
//# sourceMappingURL=git.js.map