/**
 * `agents sessions share <id>` — publish one session transcript as a link.
 *
 * Composes three pieces that already existed but were never wired together:
 * `renderSessionMarkdownDocument()` (redacted transcript),
 * `renderSessionHtmlDocument()` (self-contained branded page), and
 * `publishFile()` (the R2-backed share Worker). Before this, sharing a session
 * meant three commands and a detour through the external artifacts-cli.
 *
 * Unlisted by default, unlike `agents artifacts share`. A transcript carries file
 * paths, command output, error text, and whatever a tool printed — strictly more
 * than a plan does — so it does not belong in the public `/<user>` gallery unless
 * the operator asks for it with `--public`. The URL itself stays world-readable:
 * unlisted is a capability URL, not a secret.
 */
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import chalk from 'chalk';
import { setHelpSections } from '../lib/help.js';
import { knownSecretValuesFromEnv } from '../lib/redact.js';
import { discoverSessions } from '../lib/session/discover.js';
import { renderSessionHtmlDocument } from '../lib/session/share-html.js';
import { publishFile, redactEmails } from '../lib/share/publish.js';
import { renderSessionMarkdownDocument } from './sessions-render.js';
import { selectSessions } from './sessions-export.js';
import { parseAgentFilter } from './sessions.js';
function parseReasoning(value) {
    if (value === 'omit' || value === 'fold' || value === 'include')
        return value;
    throw new Error(`Unknown reasoning mode "${value}". Expected omit, fold, or include.`);
}
/** `session-<shortId>` — stable per session, so re-sharing updates the same URL. */
export function defaultSessionSlug(session) {
    return `session-${session.shortId || session.id}`;
}
/**
 * Map the command's flags onto {@link PublishOptions}.
 *
 * Extracted so the defaults are testable directly. `unlisted` in particular is the
 * security-relevant one — it inverts the parent command's public-by-default
 * behavior, and a test that re-implements this mapping would still pass with it
 * flipped.
 */
export function buildSharePublishOptions(session, options) {
    return {
        slug: options.slug ?? defaultSessionSlug(session),
        unlisted: options.public !== true,
        expire: options.expire,
        force: options.force === true,
        cover: options.cover !== false,
        label: options.label,
        meta: { kind: 'session' },
    };
}
export function registerSessionsShareCommand(sessionsCmd) {
    const cmd = sessionsCmd
        .command('share <session>')
        .description('Publish one session as a redacted, self-contained web page and print the link.')
        .option('--public', 'List the page in your public share gallery (default: unlisted capability URL)')
        .option('--slug <slug>', 'URL slug under your namespace (default: session-<shortId>)')
        .option('--label <text>', 'Display title in the gallery and `agents artifacts share list`')
        .option('--expire <spec>', 'Auto-expire window: 30d, 12h, a date, or never (default: 30d)')
        .option('--reasoning <mode>', 'Reasoning visibility: omit, fold, or include', 'omit')
        .option('--force', 'Publish despite the sensitive-content scan flagging the transcript')
        .option('--no-cover', 'Skip generating the Open Graph preview image');
    setHelpSections(cmd, {
        examples: `# Share a session — prints an unlisted, redacted link
agents sessions share a1b2c3d4

# Put it in your public gallery at share.getrush.ai/<you>
agents sessions share a1b2c3d4 --public --label "How the retry bug got fixed"

# Keep the model's reasoning in collapsible sections
agents sessions share a1b2c3d4 --reasoning fold

# A link that does not decay
agents sessions share a1b2c3d4 --expire never`,
        notes: `Requires a share endpoint: 'agents artifacts share status' shows it, 'agents artifacts setup'
provisions one, 'agents artifacts share join <baseUrl>' uses an existing one.

Unlisted by default — the URL is world-readable but the page stays out of your public
gallery and out of 'agents artifacts share list'. Pass --public to list it.

Secrets are redacted and the same pre-publish scan as 'agents artifacts share' runs on the
page, so a transcript carrying emails or credential-shaped strings is refused
unless you pass --force. --no-redact (the sessions-level flag) disables redaction
and is a bad idea for anything you publish.

Re-running with the same session updates the same URL and keeps the prior version
as a revision ('agents artifacts share revisions <slug>').

Manage published sessions with 'agents artifacts share list' and
'agents artifacts share delete <slug>'.`,
    });
    cmd.action(async (selector, options, command) => {
        const globals = command.optsWithGlobals();
        const reasoning = parseReasoning(options.reasoning);
        const limit = Math.max(1, Number.parseInt(globals.limit || '100', 10) || 100);
        const agent = parseAgentFilter(globals.agent).agent;
        const sessions = selectSessions(await discoverSessions({
            all: globals.all !== false,
            since: globals.since,
            limit,
            agent: agent ?? undefined,
        }), [selector]);
        if (sessions.length === 0) {
            process.stderr.write(chalk.yellow(`No session matched "${selector}".\n`));
            process.exitCode = 1;
            return;
        }
        // One link per share: several sessions in one page would give the reader no
        // way to reference just the one that matters, and the slug could only name one.
        if (sessions.length > 1) {
            process.stderr.write(chalk.yellow(`"${selector}" matched ${sessions.length} sessions. Share one at a time — pass a full or unique session id.\n`));
            process.exitCode = 1;
            return;
        }
        const session = sessions[0];
        const redact = globals.redact !== false;
        const markdown = renderSessionMarkdownDocument(session, {
            redact,
            reasoning,
            knownSecrets: redact ? knownSecretValuesFromEnv() : undefined,
        });
        // Emails on top of what the renderer masks. Almost every real transcript
        // carries a few — git author addresses, `gh api user`, a pasted log — and
        // publishToEndpoint refuses a body containing any (RUSH-2428). Masking them
        // means the published page genuinely does not carry them; the alternative,
        // telling people to pass --force, would train everyone to bypass the gate
        // that also catches real credentials.
        //
        // Applied to the RENDERED PAGE, not the Markdown, so the text that is masked
        // is exactly the text `scanShareContent` will scan. Markdown escaping stands
        // between the two: `foo\@example.com` hides from the pattern in the Markdown
        // and reappears as a live address once marked drops the backslash.
        const page = renderSessionHtmlDocument(session, markdown, { redacted: redact });
        const html = redact ? redactEmails(page) : page;
        // A real file on disk is what publishFile() takes, and the OG capturer opens
        // it in a browser. 0600 + a per-run directory keeps the intermediate off a
        // world-readable /tmp path while it exists.
        const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'agents-session-share-'));
        const file = path.join(dir, `${defaultSessionSlug(session)}.html`);
        try {
            fs.writeFileSync(file, html, { mode: 0o600 });
            const result = await publishFile(file, buildSharePublishOptions(session, options));
            if (globals.json) {
                process.stdout.write(JSON.stringify({
                    session: session.id,
                    agent: session.agent,
                    redacted: redact,
                    ...result,
                }, null, 2) + '\n');
                return;
            }
            process.stdout.write(`${result.url}\n`);
            const bits = [result.unlisted ? 'unlisted' : 'public', redact ? 'redacted' : chalk.red('NOT redacted')];
            if (result.expiresAt)
                bits.push(`expires ${result.expiresAt.slice(0, 10)}`);
            process.stderr.write(chalk.dim(`${bits.join(' · ')}\n`));
        }
        finally {
            fs.rmSync(dir, { recursive: true, force: true });
        }
    });
}
