"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.detectBoundPort = detectBoundPort;
exports.getListeningPid = getListeningPid;
exports.getProcessCwd = getProcessCwd;
exports.isSameOrDescendant = isSameOrDescendant;
/**
 * Port detection — uses `lsof` to check whether a local TCP port is bound,
 * and (optionally) whether the bound process is running out of the user's
 * project directory. Used by the data-ingestion screen to point users at the
 * URL where *their* dev server is actually running, not a hardcoded default
 * and not someone else's service that happens to sit on :3000.
 *
 * Why cwd-match: without it we'd treat any listener on a candidate port as
 * "the user's app" — Docker, a sibling project's dev server, whatever — and
 * send them to a URL that has nothing to do with the wizard run.
 */
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const LSOF_TIMEOUT_MS = 500;
/**
 * Returns the first port from `candidates` that passes all checks. Returns
 * null if none do or `lsof` is unavailable.
 */
async function detectBoundPort(candidates, options = {}) {
    for (const port of candidates) {
        const pid = await getListeningPid(port);
        if (pid === null)
            continue;
        if (options.cwd !== undefined) {
            const procCwd = await getProcessCwd(pid);
            if (procCwd === null)
                continue;
            if (!isSameOrDescendant(procCwd, options.cwd))
                continue;
        }
        return port;
    }
    return null;
}
/** PID of the process listening on `port`, or null if none. */
function getListeningPid(port) {
    if (!Number.isInteger(port) || port <= 0 || port > 65535) {
        return Promise.resolve(null);
    }
    return runLsof(`lsof -iTCP:${port} -sTCP:LISTEN -P -n -Fp`).then((out) => {
        if (out === null)
            return null;
        // -Fp emits one "pNNNN" line per matching process.
        const match = out.match(/^p(\d+)/m);
        return match ? Number(match[1]) : null;
    });
}
/** Working directory of `pid`, or null if unknown. */
function getProcessCwd(pid) {
    if (!Number.isInteger(pid) || pid <= 0)
        return Promise.resolve(null);
    return runLsof(`lsof -p ${pid} -a -d cwd -Fn`).then((out) => {
        if (out === null)
            return null;
        // -Fn emits an "n<path>" line for the cwd fd.
        const match = out.match(/^n(.+)$/m);
        return match ? match[1] : null;
    });
}
/**
 * True if `child` equals `parent` or sits beneath it on disk. Both sides are
 * canonicalized via `fs.realpathSync` before comparison so symlinked install
 * dirs (common with macOS Conductor worktrees, iCloud Drive, Volumes) match
 * the canonical cwd that lsof reports for the listener.
 */
function isSameOrDescendant(child, parent) {
    const c = canonical(child);
    const p = canonical(parent);
    if (c === p)
        return true;
    const withSep = p.endsWith(path.sep) ? p : p + path.sep;
    return c.startsWith(withSep);
}
/** Resolve symlinks if the path exists; otherwise fall back to path.resolve. */
function canonical(p) {
    try {
        return fs.realpathSync(p);
    }
    catch {
        return path.resolve(p);
    }
}
function runLsof(command) {
    return new Promise((resolve) => {
        const child = (0, child_process_1.exec)(command, { timeout: LSOF_TIMEOUT_MS }, (error, stdout) => {
            // lsof exits 1 when no match — treat any non-zero exit or error as
            // "no information", distinct from "ran and produced output".
            if (error) {
                resolve(null);
                return;
            }
            resolve(stdout);
        });
        child.on('error', () => resolve(null));
    });
}
