import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import readline from 'node:readline';
import { textEncode } from "../../src/domain/utils/bytes.js";
import _GitPlumbing, { ShellRunnerFactory as _ShellRunnerFactory } from '@git-stunts/plumbing';
const _sfRaw = _ShellRunnerFactory;
const TypedShellRunnerFactory = _sfRaw;
const _gpRaw = _GitPlumbing;
const TypedGitPlumbing = _gpRaw;
import GitGraphAdapter from "../../src/infrastructure/adapters/GitGraphAdapter.js";
import WebCryptoAdapter from "../../src/infrastructure/adapters/WebCryptoAdapter.js";
import { openRuntimeHostProduct } from "../../src/domain/warp/RuntimeHostProduct.js";
import { REF_PREFIX, buildCursorActiveRef, } from "../../src/domain/utils/RefLayout.js";
import CasSeekCacheAdapter from "../../src/infrastructure/adapters/CasSeekCacheAdapter.js";
import { HookInstaller } from "../../src/domain/services/HookInstaller.js";
import PlumbingHookPathAdapter from "../../src/infrastructure/adapters/PlumbingHookPathAdapter.js";
import { parseCursorBlob } from "../../src/domain/utils/parseCursorBlob.js";
import { usageError, notFoundError } from "./infrastructure.js";
function createPlumbing(repoPath) {
    const runner = TypedShellRunnerFactory.create();
    const plumbing = new TypedGitPlumbing({ cwd: repoPath, runner });
    return plumbing;
}
/**
 * Creates a persistence adapter for the given repository path.
 */
export async function createPersistence(repoPath) {
    const plumbing = createPlumbing(repoPath);
    const persistence = new GitGraphAdapter({ plumbing });
    const ping = await persistence.ping();
    if (!ping.ok) {
        throw usageError(`Repository not accessible: ${repoPath}`);
    }
    return { persistence, plumbing };
}
/**
 * Lists all graph names found under the WARP ref prefix.
 */
export async function listGraphNames(persistence) {
    if (typeof persistence.listRefs !== 'function') {
        return [];
    }
    const refs = await persistence.listRefs(REF_PREFIX);
    const prefix = `${REF_PREFIX}/`;
    const names = new Set();
    for (const ref of refs) {
        if (!ref.startsWith(prefix)) {
            continue;
        }
        const rest = ref.slice(prefix.length);
        const [graphName] = rest.split('/');
        if (typeof graphName === 'string' && graphName.length > 0) {
            names.add(graphName);
        }
    }
    return ([...names].sort());
}
/**
 * Resolves the graph name from an explicit flag or auto-detects a single graph.
 */
export async function resolveGraphName(persistence, explicitGraph) {
    if (typeof explicitGraph === 'string' && explicitGraph.length > 0) {
        return explicitGraph;
    }
    const graphNames = await listGraphNames(persistence);
    if (graphNames.length === 1) {
        return graphNames[0];
    }
    if (graphNames.length === 0) {
        throw notFoundError('No graphs found in repo; specify --graph');
    }
    throw usageError('Multiple graphs found; specify --graph');
}
/**
 * Opens a WarpCore for the given CLI options.
 */
export async function openGraph(options) {
    const { persistence, plumbing } = await createPersistence(options.repo);
    const graphName = await resolveGraphName(persistence, options.graph);
    if (typeof options.graph === 'string' && options.graph.length > 0) {
        const graphNames = await listGraphNames(persistence);
        if (!graphNames.includes(options.graph)) {
            throw notFoundError(`Graph not found: ${options.graph}`);
        }
    }
    const graph = await openRuntimeHostProduct({
        persistence,
        graphName,
        writerId: options.writer,
        crypto: new WebCryptoAdapter(),
    });
    return { graph, graphName, persistence, plumbing };
}
/**
 * Reads the active cursor and sets `_seekCeiling` on the graph instance
 * so that subsequent materialize calls respect the time-travel boundary.
 */
export async function applyCursorCeiling(graph, persistence, graphName) {
    const cursor = await readActiveCursor(persistence, graphName);
    if (cursor) {
        graph._seekCeiling = cursor.tick;
        return { active: true, tick: cursor.tick, maxTick: null };
    }
    return { active: false, tick: null, maxTick: null };
}
/**
 * Prints a seek cursor warning banner to stderr when a cursor is active.
 */
export function emitCursorWarning(cursorInfo, maxTick) {
    if (cursorInfo.active) {
        const tickLabel = cursorInfo.tick !== null ? String(cursorInfo.tick) : 'not set';
        const maxLabel = maxTick !== null && maxTick !== undefined ? ` of ${maxTick}` : '';
        process.stderr.write(`\u26A0 seek active (tick ${tickLabel}${maxLabel}) \u2014 run "git warp seek --latest" to return to present\n`);
    }
}
/**
 * Reads the active seek cursor for a graph from Git ref storage.
 */
export async function readActiveCursor(persistence, graphName) {
    const ref = buildCursorActiveRef(graphName);
    const oid = await persistence.readRef(ref);
    if (typeof oid !== 'string' || oid.length === 0) {
        return null;
    }
    const buf = await persistence.readBlob(oid);
    return parseCursorBlob(buf, 'active cursor');
}
/**
 * Writes (creates or overwrites) the active seek cursor for a graph.
 */
export async function writeActiveCursor(persistence, graphName, cursor) {
    const ref = buildCursorActiveRef(graphName);
    const json = JSON.stringify(cursor);
    const oid = await persistence.writeBlob(textEncode(json));
    await persistence.updateRef(ref, oid);
}
/**
 * Reads the commit date from a checkpoint SHA, if available.
 */
export async function readCheckpointDate(persistence, checkpointSha) {
    if (typeof checkpointSha !== 'string' || checkpointSha.length === 0) {
        return null;
    }
    const info = await persistence.getNodeInfo(checkpointSha);
    return (typeof info.date === 'string' && info.date.length > 0) ? info.date : null;
}
/**
 * Create a HookInstaller wired with real filesystem dependencies.
 */
export function createHookInstaller() {
    const packageRoot = findPackageRoot(fileURLToPath(new URL('.', import.meta.url)));
    const templateDir = path.join(packageRoot, 'scripts', 'hooks');
    const rawJson = fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8');
    const version = readPackageVersion(rawJson);
    return new HookInstaller({
        fs: fs,
        hookPathPort: new PlumbingHookPathAdapter({
            plumbingFactory: { create: createPlumbing },
            path,
        }),
        version,
        templateDir,
        path,
    });
}
/**
 * Finds the repository/package root from either source or built CLI paths.
 */
function findPackageRoot(startDir) {
    let current = startDir;
    while (true) {
        if (fs.existsSync(path.join(current, 'package.json'))) {
            return current;
        }
        const parent = path.dirname(current);
        if (parent === current) {
            throw usageError('Unable to locate package.json for hook installation');
        }
        current = parent;
    }
}
/**
 * Check whether stderr is a TTY (interactive terminal).
 */
export function isInteractive() {
    return Boolean(process.stderr.isTTY);
}
/**
 * Prompts the user for input via stderr and returns the trimmed response.
 */
export function promptUser(question) {
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stderr,
    });
    return new Promise((resolve) => {
        rl.question(question, (answer) => {
            rl.close();
            resolve(answer.trim());
        });
    });
}
/**
 * Extracts the version string from raw package.json content.
 */
function readPackageVersion(rawJson) {
    const raw = JSON.parse(rawJson);
    const obj = raw;
    return obj.version;
}
/**
 * Attaches a persistent seek cache to a graph instance unless disabled by flags.
 */
export function wireSeekCache({ graph, persistence, plumbing, graphName, seekSpec }) {
    if (seekSpec.noPersistentCache) {
        return;
    }
    graph.setSeekCache(new CasSeekCacheAdapter({
        persistence,
        plumbing,
        graphName,
    }));
}
