/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { spawn } from 'node:child_process';
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { basename, join } from 'node:path';
import { PLUGIN_VERSION } from '../version.js';
import { importWorkspace } from './export-import.js';
export const STATE_SUBDIR = join('.setup-agents', 'state');
export async function pushToGist(stateDir, gistId, dryRun) {
    const jsonlFiles = readJsonlFiles(stateDir);
    if (dryRun) {
        const fileNames = jsonlFiles.map((f) => f.name);
        return { pushed: fileNames, gistId: gistId ?? '(new)', url: '(dry-run)' };
    }
    if (jsonlFiles.length === 0) {
        return { pushed: [], gistId: gistId ?? '', url: '' };
    }
    let resolvedGistId = gistId;
    let url = '';
    if (!resolvedGistId) {
        const cwdBasename = basename(process.cwd());
        const descArg = `setup-agents state: ${cwdBasename}`;
        const createOut = await ghWithStdin(['gist', 'create', '--private', '--desc', descArg, '--filename', jsonlFiles[0].name, '-'], jsonlFiles[0].content);
        resolvedGistId = extractGistId(createOut.trim());
        url = createOut.trim();
        // Sequential: gh gist edit calls must not race on the same gist
        await jsonlFiles.slice(1).reduce(async (prev, file) => {
            await prev;
            await ghWithStdin(['gist', 'edit', resolvedGistId, '--add', file.name], file.content);
        }, Promise.resolve());
    }
    else {
        // Sequential: each edit/add must complete before the next
        await jsonlFiles.reduce(async (prev, file) => {
            await prev;
            try {
                await ghWithStdin(['gist', 'edit', resolvedGistId, '--filename', file.name], file.content);
            }
            catch {
                await ghWithStdin(['gist', 'edit', resolvedGistId, '--add', file.name], file.content);
            }
        }, Promise.resolve());
        url = await gh(['gist', 'view', resolvedGistId]).catch(() => `https://gist.github.com/${resolvedGistId ?? ''}`);
        if (!url.trim())
            url = `https://gist.github.com/${resolvedGistId ?? ''}`;
    }
    return {
        pushed: jsonlFiles.map((f) => f.name),
        gistId: resolvedGistId,
        url,
    };
}
export async function pullFromGist(stateDir, gistId, dryRun) {
    const listOut = await gh(['gist', 'view', gistId, '--files']);
    const fileNames = listOut
        .trim()
        .split('\n')
        .map((l) => l.trim())
        .filter((l) => l.endsWith('.jsonl'));
    if (fileNames.length === 0) {
        return { pulled: [], conflicts: 0 };
    }
    // Fetch all files in parallel, then process results sequentially
    const rawContents = await Promise.all(fileNames.map((fileName) => gh(['gist', 'view', gistId, '--filename', fileName, '--raw'])));
    const pulled = [];
    let totalConflicts = 0;
    for (const [i, rawContent] of rawContents.entries()) {
        if (!rawContent.trim())
            continue;
        const remoteRecords = rawContent
            .trim()
            .split('\n')
            .map((line) => {
            try {
                return JSON.parse(line);
            }
            catch {
                return null;
            }
        })
            .filter((r) => r !== null);
        if (remoteRecords.length === 0)
            continue;
        const bundle = buildBundleFromRecords(fileNames[i], remoteRecords, stateDir);
        const result = importWorkspace(stateDir, bundle, { onConflict: 'skip', dryRun });
        const imported = result.imported.tasks +
            result.imported.decisions +
            result.imported.evidence +
            result.imported.handoffs +
            result.imported.reviews;
        const skipped = result.skipped.tasks +
            result.skipped.decisions +
            result.skipped.evidence +
            result.skipped.handoffs +
            result.skipped.reviews;
        if (imported > 0)
            pulled.push(fileNames[i]);
        totalConflicts += skipped;
    }
    return { pulled, conflicts: totalConflicts };
}
function readJsonlFiles(stateDir) {
    if (!existsSync(stateDir))
        return [];
    return readdirSync(stateDir)
        .filter((f) => f.endsWith('.jsonl'))
        .filter((f) => statSync(join(stateDir, f)).size > 0)
        .map((f) => ({
        name: f,
        content: readFileSync(join(stateDir, f), 'utf8'),
    }));
}
function extractGistId(output) {
    const match = /gist\.github\.com\/[^/]+\/([a-f0-9]+)/i.exec(output) ?? /([a-f0-9]{20,})/i.exec(output);
    if (!match)
        throw new Error(`Could not parse gist ID from gh output: ${output}`);
    return match[1];
}
function buildBundleFromRecords(fileName, records, cwd) {
    const base = {
        version: PLUGIN_VERSION,
        exportedAt: new Date().toISOString(),
        source: cwd,
        config: null,
        state: { tasks: [], decisions: [], evidence: [], handoffs: [], reviews: [] },
    };
    if (fileName === 'tasks.jsonl') {
        base.state.tasks = records;
    }
    else if (fileName === 'decisions.jsonl') {
        base.state.decisions = records;
    }
    else if (fileName === 'evidence.jsonl') {
        base.state.evidence = records;
    }
    else if (fileName === 'handoffs.jsonl') {
        base.state.handoffs = records;
    }
    else if (fileName === 'reviews.jsonl') {
        base.state.reviews = records;
    }
    return base;
}
/** Run a gh command without stdin, return stdout. */
function gh(args) {
    return new Promise((resolve, reject) => {
        const proc = spawn('gh', args, { stdio: ['ignore', 'pipe', 'pipe'] });
        let out = '';
        let err = '';
        proc.stdout.on('data', (d) => {
            out += d.toString();
        });
        proc.stderr.on('data', (d) => {
            err += d.toString();
        });
        proc.on('close', (code) => {
            if (code === 0) {
                resolve(out);
            }
            else {
                reject(new Error(`gh ${args.join(' ')} failed (exit ${String(code)}): ${err}`));
            }
        });
    });
}
/** Run a gh command with content piped to stdin, return stdout. */
function ghWithStdin(args, input) {
    return new Promise((resolve, reject) => {
        const proc = spawn('gh', args, { stdio: ['pipe', 'pipe', 'pipe'] });
        let out = '';
        let err = '';
        proc.stdout.on('data', (d) => {
            out += d.toString();
        });
        proc.stderr.on('data', (d) => {
            err += d.toString();
        });
        proc.on('close', (code) => {
            if (code === 0) {
                resolve(out);
            }
            else {
                reject(new Error(`gh ${args.join(' ')} failed (exit ${String(code)}): ${err}`));
            }
        });
        proc.stdin.write(input);
        proc.stdin.end();
    });
}
//# sourceMappingURL=remote-sync.js.map