import { execFile } from "node:child_process";
import { detectProjectMemorySecretRule, } from "./project-memory.js";
const DEFAULT_TEAM_MEMORY_SYNC_DEBOUNCE_MS = 10_000;
const TEAM_MEMORY_SYNC_TIMEOUT_MS = 10_000;
export class ProjectMemorySyncClient {
    #cwd;
    #debounceMs;
    #fetchFn;
    #log;
    #projectMemory;
    #status;
    #token;
    #url;
    #projectKey;
    #projectKeyLoaded = false;
    #queued = false;
    #syncPromise = null;
    #timer;
    constructor(options) {
        this.#cwd = options.cwd;
        this.#debounceMs = options.debounceMs ?? DEFAULT_TEAM_MEMORY_SYNC_DEBOUNCE_MS;
        this.#fetchFn = options.fetchFn ?? fetch;
        this.#log = options.log ?? console;
        this.#projectMemory = options.projectMemory;
        this.#token = options.token?.trim() || undefined;
        this.#url = normalizeTeamMemoryUrl(options.url);
        this.#status = this.#buildInitialStatus();
    }
    close() {
        if (this.#timer) {
            clearTimeout(this.#timer);
            this.#timer = undefined;
        }
    }
    prepareForSession() {
        if (!this.#url) {
            return;
        }
        void this.syncNow();
    }
    getStatus() {
        return { ...this.#status };
    }
    schedulePush() {
        if (!this.#url) {
            return;
        }
        if (this.#timer) {
            clearTimeout(this.#timer);
        }
        this.#timer = setTimeout(() => {
            this.#timer = undefined;
            void this.syncNow();
        }, this.#debounceMs);
        this.#timer.unref?.();
    }
    async syncNow() {
        if (!this.#url) {
            return;
        }
        if (this.#syncPromise) {
            this.#queued = true;
            return this.#syncPromise;
        }
        this.#syncPromise = this.#runSyncLoop().finally(() => {
            this.#syncPromise = null;
        });
        return this.#syncPromise;
    }
    async #runSyncLoop() {
        do {
            this.#queued = false;
            try {
                await this.#synchronizeOnce();
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                this.#status.lastError = message;
                this.#log.warn(`[best-agent] Team memory sync skipped: ${message}`);
            }
        } while (this.#queued);
    }
    async #synchronizeOnce() {
        const projectKey = await this.#ensureProjectKey();
        if (!projectKey) {
            return;
        }
        const serverEntries = await this.#fetchEntries(projectKey);
        this.#status.lastError = undefined;
        this.#status.lastPullAt = new Date().toISOString();
        this.#projectMemory.mergeSyncEntries(serverEntries);
        const latestServerEntries = new Map(serverEntries.map((entry) => [entry.key, entry]));
        const patchEntries = [];
        for (const localEntry of this.#projectMemory.listSyncEntries()) {
            const blockedRule = detectProjectMemorySecretRule(localEntry.content);
            if (blockedRule) {
                this.#log.warn(`[best-agent] Team memory upload skipped for ${localEntry.key}: blocked secret pattern (${blockedRule}).`);
                continue;
            }
            const serverEntry = latestServerEntries.get(localEntry.key);
            if (serverEntry && serverEntry.checksum === localEntry.checksum) {
                continue;
            }
            if (serverEntry && serverEntry.checksum !== localEntry.checksum) {
                this.#status.lastConflictAt = new Date().toISOString();
                this.#log.warn(`[best-agent] Team memory conflict for ${localEntry.key}; applying last-write-wins upload.`);
            }
            patchEntries.push({
                checksum: localEntry.checksum,
                content: localEntry.content,
                key: localEntry.key,
            });
        }
        if (patchEntries.length === 0) {
            return;
        }
        await this.#patchEntries(projectKey, patchEntries);
        this.#status.lastPushAt = new Date().toISOString();
    }
    async #fetchEntries(projectKey) {
        const response = await this.#fetchJson(`/api/v1/team-memory?project=${encodeURIComponent(projectKey)}`);
        const entries = Array.isArray(response.entries) ? response.entries : [];
        return entries.filter((entry) => Boolean(entry) &&
            typeof entry.checksum === "string" &&
            typeof entry.content === "string" &&
            typeof entry.key === "string" &&
            typeof entry.updatedAt === "string");
    }
    async #patchEntries(projectKey, entries) {
        await this.#fetchJson(`/api/v1/team-memory?project=${encodeURIComponent(projectKey)}`, {
            body: JSON.stringify({ entries }),
            method: "PATCH",
        });
    }
    async #fetchJson(path, init = {}) {
        const headers = new Headers(init.headers);
        if (!headers.has("content-type") && init.body !== undefined) {
            headers.set("content-type", "application/json");
        }
        if (this.#token) {
            headers.set("authorization", `Bearer ${this.#token}`);
        }
        const controller = new AbortController();
        const timeout = setTimeout(() => {
            controller.abort();
        }, TEAM_MEMORY_SYNC_TIMEOUT_MS);
        try {
            const response = await this.#fetchFn(`${this.#url}${path}`, {
                ...init,
                headers,
                signal: controller.signal,
            });
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            return (await response.json());
        }
        finally {
            clearTimeout(timeout);
        }
    }
    async #ensureProjectKey() {
        if (!this.#url) {
            return undefined;
        }
        if (!this.#projectKeyLoaded) {
            this.#projectKey = await readProjectKey(this.#cwd);
            this.#projectKeyLoaded = true;
            if (this.#projectKey) {
                this.#status.enabled = true;
                this.#status.mode = "shared";
                this.#status.projectKey = this.#projectKey;
                this.#status.reason = undefined;
            }
            else {
                this.#status.enabled = false;
                this.#status.mode = "local-only";
                this.#status.projectKey = undefined;
                this.#status.reason = "missing-git-remote";
            }
        }
        return this.#projectKey;
    }
    #buildInitialStatus() {
        if (!this.#url) {
            return {
                enabled: false,
                mode: "local-only",
                reason: "missing-url",
            };
        }
        return {
            enabled: false,
            mode: "local-only",
            reason: "missing-git-remote",
        };
    }
}
export function normalizeTeamMemoryProjectKey(remoteUrl) {
    const trimmed = remoteUrl.trim();
    if (!trimmed) {
        return undefined;
    }
    const sshMatch = trimmed.match(/^(?:ssh:\/\/)?git@([^:/]+)[:/]([^#?]+?)(?:\.git)?$/i);
    if (sshMatch) {
        return `${sshMatch[1]}/${sshMatch[2]}`.replace(/\/+$/u, "").toLowerCase();
    }
    try {
        const url = new URL(trimmed);
        const path = url.pathname
            .replace(/^\//u, "")
            .replace(/\.git$/iu, "")
            .replace(/\/+$/u, "");
        return path ? `${url.host}/${path}`.toLowerCase() : undefined;
    }
    catch {
        const normalized = trimmed
            .replace(/^[a-z]+:\/\//iu, "")
            .replace(/\.git$/iu, "")
            .replace(/\/+$/u, "")
            .toLowerCase();
        return normalized || undefined;
    }
}
function normalizeTeamMemoryUrl(url) {
    const trimmed = url?.trim();
    if (!trimmed) {
        return undefined;
    }
    return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
}
function readProjectKey(cwd) {
    return new Promise((resolveProjectKey) => {
        execFile("git", ["config", "--get", "remote.origin.url"], {
            cwd,
            encoding: "utf8",
        }, (error, stdout) => {
            if (error) {
                resolveProjectKey(undefined);
                return;
            }
            resolveProjectKey(normalizeTeamMemoryProjectKey(stdout.trim()));
        });
    });
}
//# sourceMappingURL=project-memory-sync.js.map