"use strict";
/**
 * RemotePublisher — Story 14.1 (v1.5)
 *
 * Pushes a sealed asset (Squad or Skill) to a remote registry over HTTPS.
 * OpenLife does not yet bundle a registry of its own — this module ships
 * the client-side surface so any HTTPS endpoint that accepts an
 * authenticated PUT can serve as the registry.
 *
 * Wire-up:
 *   • Activate by setting OPENLIFE_REMOTE_PUBLISH_URL (base URL).
 *   • Optional OPENLIFE_REMOTE_PUBLISH_TOKEN — sent as `Authorization: Bearer ...`.
 *   • Each publish request is: PUT <base>/<kind>/<id>/<sha256>.md
 *     with the asset markdown as the request body and the SHA-256 in
 *     the `X-Asset-Sha256` header so the server can verify integrity.
 *
 * Failure shape: never throws. Returns
 *   { ok: false, error: '<stable-code>', detail?: '<short>' }
 * with stable codes:
 *   • 'remote_publish_not_configured'  — no URL env var set
 *   • 'invalid_url'                    — base URL is malformed
 *   • 'fetch_error'                    — network / abort
 *   • 'http_<status>'                  — non-2xx response
 *   • 'asset_not_found'                — local file disappeared mid-flight
 *   • 'sha_mismatch_local'             — local re-hash != provided sha256
 */
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.RemotePublisher = void 0;
const fs = __importStar(require("fs"));
class RemotePublisher {
    baseUrl;
    token;
    fetchImpl;
    timeoutMs;
    constructor(opts = {}) {
        this.baseUrl = opts.baseUrl ?? (process.env.OPENLIFE_REMOTE_PUBLISH_URL || '');
        this.token = opts.token ?? (process.env.OPENLIFE_REMOTE_PUBLISH_TOKEN || '');
        this.fetchImpl = opts.fetchImpl ?? globalThis.fetch;
        this.timeoutMs = opts.timeoutMs ?? 15_000;
    }
    isConfigured() {
        return this.baseUrl.trim().length > 0;
    }
    async publishAsset(kind, id, sha256, file) {
        if (!this.isConfigured()) {
            return { ok: false, error: 'remote_publish_not_configured', detail: 'set OPENLIFE_REMOTE_PUBLISH_URL' };
        }
        const url = this.buildUrl(kind, id, sha256);
        if (!url)
            return { ok: false, error: 'invalid_url' };
        if (!fs.existsSync(file))
            return { ok: false, error: 'asset_not_found', detail: file };
        let body;
        try {
            body = fs.readFileSync(file, 'utf-8');
        }
        catch (err) {
            return { ok: false, error: 'asset_not_found', detail: err instanceof Error ? err.message : String(err) };
        }
        // Defensive: re-hash locally to make sure the caller didn't drift.
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        const crypto = require('crypto');
        const computed = crypto.createHash('sha256').update(body).digest('hex');
        if (computed !== sha256) {
            return { ok: false, error: 'sha_mismatch_local', detail: `expected=${sha256.slice(0, 12)}… computed=${computed.slice(0, 12)}…` };
        }
        const headers = {
            'Content-Type': 'text/markdown',
            'X-Asset-Sha256': sha256,
            'X-Asset-Kind': kind,
            'X-Asset-Id': id,
        };
        if (this.token)
            headers.Authorization = `Bearer ${this.token}`;
        const ac = new AbortController();
        const timer = setTimeout(() => ac.abort(), this.timeoutMs);
        try {
            const response = await this.fetchImpl(url, { method: 'PUT', headers, body, signal: ac.signal });
            const status = response.status;
            const etag = response.headers.get?.('etag') || undefined;
            if (status < 200 || status >= 300) {
                return { ok: false, url, status, error: `http_${status}` };
            }
            return { ok: true, url, status, etag };
        }
        catch (err) {
            const detail = err instanceof Error ? err.message : String(err);
            return { ok: false, error: 'fetch_error', detail };
        }
        finally {
            clearTimeout(timer);
        }
    }
    buildUrl(kind, id, sha256) {
        try {
            const base = new URL(this.baseUrl);
            // Always append a clean path; ignore any base path hidden in baseUrl
            const trimmed = base.toString().replace(/\/+$/, '');
            return `${trimmed}/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/${encodeURIComponent(sha256)}.md`;
        }
        catch {
            return null;
        }
    }
}
exports.RemotePublisher = RemotePublisher;
