<?php

declare(strict_types=1);

/**
 * APO-197 Cards 5 + 6 — Operator Permission UI for AI integrations.
 *
 * List + create + view + revoke + rotate.
 *
 * Newly-created tokens (and rotated tokens) are surfaced on the success
 * screen ONCE via a flash + session-stash; subsequent loads of the manage
 * page do NOT show the raw token. The session-stash key is namespaced by
 * integration id so a copy-paste user can refresh once and still see it.
 */

require_once dirname(__DIR__, 3) . '/system/bootstrap.php';
require_once dirname(__DIR__, 3) . '/system/mcp/Server.php';

$currentUser = myronRequireAdministrationAccess();
$repo = myronAiIntegrationRepository();
$oauthRepo = myronMcpOAuthClientRepository();
$revocationService = myronAiConnectionRevocationService();
$csrfToken = myronCsrfToken();
$manageId = trim((string) ($_GET['manage'] ?? ''));
$flashMessage = '';

myronEnsureSession();
$stashedTokens = isset($_SESSION['myron_ai_one_time_tokens']) && is_array($_SESSION['myron_ai_one_time_tokens'])
    ? $_SESSION['myron_ai_one_time_tokens']
    : [];

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
    try {
        myronRequireCsrf($_POST['csrf_token'] ?? null);
        $action = trim((string) ($_POST['action'] ?? ''));

        if ($action === 'create-ai-connection') {
            $label = trim((string) ($_POST['label'] ?? ''));
            $scopes = is_array($_POST['scopes'] ?? null) ? array_values(array_map('strval', $_POST['scopes'])) : [];
            $preset = trim((string) ($_POST['scope_preset'] ?? ''));
            if ($preset !== '' && $scopes === []) {
                $scopes = match ($preset) {
                    'read-everything' => [
                        MyronApiScopes::DESIGN_READ,
                        MyronApiScopes::CONTENT_READ,
                        MyronApiScopes::ADMINISTRATION_READ,
                    ],
                    'author-content' => [
                        MyronApiScopes::DESIGN_READ,
                        MyronApiScopes::CONTENT_READ,
                        MyronApiScopes::CONTENT_WRITE,
                    ],
                    'full-access' => MyronApiScopes::ALL_BASE,
                    default => [],
                };
            }
            $rateLimitConfig = null;
            $rpm = (int) ($_POST['requests_per_minute'] ?? 0);
            if ($rpm > 0) {
                $rateLimitConfig = [
                    'requestsPerMinute' => $rpm,
                    'burstCapacity'     => (int) ($_POST['burst_capacity'] ?? 20),
                ];
            }
            $created = $repo->create($label, $scopes, (string) ($currentUser['id'] ?? ''), $rateLimitConfig);
            // Stash the raw token in session keyed by integration id (shown once on next load).
            $stashedTokens[$created['integration']->id] = $created['rawToken'];
            $_SESSION['myron_ai_one_time_tokens'] = $stashedTokens;
            myronRedirectWithFlash(
                myronBuildAdminUrl('administration.aiConnections', ['manage' => $created['integration']->id, 'show_token' => '1']),
                'AI connection created. Copy the token shown below — this is the only time you will see it.',
                'success'
            );
        }

        if ($action === 'revoke-ai-connection') {
            if (!myronUserHoldsScope($currentUser, MyronApiScopes::ADMINISTRATION_DESTRUCTIVE)) {
                throw new RuntimeException('Revoking AI connections requires destructive Administration access.');
            }
            $id = trim((string) ($_POST['integration_id'] ?? ''));
            $revocationService->revoke($id);
            unset($stashedTokens[$id]);
            $_SESSION['myron_ai_one_time_tokens'] = $stashedTokens;
            myronRedirectWithFlash(myronBuildAdminUrl('administration.aiConnections'), 'AI connection revoked.', 'success');
        }

        if ($action === 'revoke-oauth-connection') {
            if (!myronUserHoldsScope($currentUser, MyronApiScopes::ADMINISTRATION_DESTRUCTIVE)) {
                throw new RuntimeException('Revoking OAuth connector access requires destructive Administration access.');
            }
            $id = trim((string) ($_POST['integration_id'] ?? ''));
            $revocationService->revoke($id);
            myronRedirectWithFlash(myronBuildAdminUrl('administration.aiConnections'), 'OAuth connector access revoked.', 'success');
        }

        if ($action === 'rotate-ai-connection') {
            if (!myronUserHoldsScope($currentUser, MyronApiScopes::ADMINISTRATION_DESTRUCTIVE)) {
                throw new RuntimeException('Rotating AI connection tokens requires destructive Administration access.');
            }
            $id = trim((string) ($_POST['integration_id'] ?? ''));
            $rotated = $repo->rotateToken($id);
            $stashedTokens[$rotated['integration']->id] = $rotated['rawToken'];
            $_SESSION['myron_ai_one_time_tokens'] = $stashedTokens;
            myronRedirectWithFlash(
                myronBuildAdminUrl('administration.aiConnections', ['manage' => $rotated['integration']->id, 'show_token' => '1']),
                'AI connection token rotated. The new token is shown below — copy it now.',
                'success'
            );
        }

        if ($action === 'update-ai-connection') {
            $id = trim((string) ($_POST['integration_id'] ?? ''));
            $label = trim((string) ($_POST['label'] ?? ''));
            $scopes = is_array($_POST['scopes'] ?? null) ? array_values(array_map('strval', $_POST['scopes'])) : [];
            $rateLimitConfig = null;
            $rpm = (int) ($_POST['requests_per_minute'] ?? 0);
            if ($rpm > 0) {
                $rateLimitConfig = [
                    'requestsPerMinute' => $rpm,
                    'burstCapacity'     => (int) ($_POST['burst_capacity'] ?? 20),
                ];
            }
            $repo->updateMetadata($id, $label, $scopes, $rateLimitConfig);
            myronRedirectWithFlash(myronBuildAdminUrl('administration.aiConnections', ['manage' => $id]), 'AI connection updated.', 'success');
        }

        throw new RuntimeException('Unknown AI connections action.');
    } catch (Throwable $exception) {
        myronRedirectWithFlash(myronBuildAdminUrl('administration.aiConnections'), $exception->getMessage(), 'error');
    }
}

// APO-204 Card 1 — UI-layer filter: hide revoked integrations from the
// operator list. The repository (listAll) intentionally returns ALL rows
// because audit-log.php needs revoked-integration labels for historical
// event display. Filtering here keeps the audit posture intact.
$integrations = array_values(array_filter(
    $repo->listAll(),
    static fn (MyronAiIntegration $i): bool => !$i->isRevoked() && $i->siteScope !== 'oauth'
));
$oauthGrants = $oauthRepo->listActiveGrantRows();
$managed = $manageId !== '' ? $repo->findById($manageId) : null;
if ($managed !== null && ($managed->isRevoked() || $managed->siteScope === 'oauth')) {
    // Revoked integrations are not addressable via ?manage=<id> from this UI.
    // Audit-log surface still resolves them by id for label display.
    $managed = null;
}
$showToken = isset($_GET['show_token']) && $_GET['show_token'] === '1';
$oneTimeToken = $managed !== null && $showToken && isset($stashedTokens[$managed->id]) ? $stashedTokens[$managed->id] : '';

myronRenderAdminShellStart($currentUser, [
    'title' => 'AI Connections',
    'titleContext' => 'Administration',
    'activeItem' => 'ai-connections',
    // APO-204 patch — files.js hosts the data-mc-copy-url + persistent-feedback
    // handler that powers the wizard tile's Copy URL button + the token reveal
    // modal's Copy Token + 'Token Copied' status. Without this include the
    // copy buttons silently no-op (no JS handler bound).
    'headScripts' => [myronBuildAssetUrl('runtime/js/files.js')],
]);
?>
<div class="mc-workspace__header">
  <?php myronRenderAdminBreadcrumbs([
      ['label' => 'Administration', 'href' => myronBuildAdminUrl('identity.users')],
      ['label' => 'AI Connections'],
  ]); ?>
  <div class="mc-workspace__title-row">
    <h1 class="mc-workspace__title">AI Connections</h1>
  </div>
  <p class="mc-workspace__subtitle">Manage AI integrations: create scoped bearer tokens for MCP clients, monitor usage, revoke or rotate compromised tokens.</p>
</div>

<?php
// APO-198 Card 7 — Connect-from-MCP wizard tile.
$mcpServerUrl = (function () {
    $scheme = ($_SERVER['HTTPS'] ?? '') === 'on' ? 'https' : 'http';
    $host = (string) ($_SERVER['HTTP_HOST'] ?? 'localhost');
    // The front controller mounts at the admin base path (e.g. /app). MCP
    // endpoint sits inside that namespace at <base>/mcp.
    $basePath = function_exists('myronAdminBasePath') ? rtrim((string) myronAdminBasePath(), '/') : '';
    return $scheme . '://' . $host . $basePath . '/mcp';
})();
$mcpVersion = MyronMcpServer::PROTOCOL_VERSION;
$oauthMetadata = myronMcpOAuthMetadataService();
$oauthProtectedResourceUrl = $oauthMetadata->protectedResourceMetadataUrl();
$oauthAuthorizationMetadataUrl = myronAppUrl() . '/.well-known/oauth-authorization-server';
$mcpTokenPlaceholder = '${MYRON_MCP_TOKEN}';
$mcpJson = static function (array $serverConfig): string {
    $encoded = json_encode(['mcpServers' => ['myroncms-dev' => $serverConfig]], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    return is_string($encoded) ? $encoded : '{}';
};
$cursorMcpJson = $mcpJson([
    'url' => $mcpServerUrl,
    'headers' => [
        'Authorization' => 'Bearer ' . $mcpTokenPlaceholder,
    ],
]);
$antigravityMcpJson = $mcpJson([
    'serverUrl' => $mcpServerUrl,
    'headers' => [
        'Authorization' => 'Bearer ' . $mcpTokenPlaceholder,
    ],
]);
$claudeCodeMcpJson = $mcpJson([
    'type' => 'http',
    'url' => $mcpServerUrl,
    'headers' => [
        'Authorization' => 'Bearer ' . $mcpTokenPlaceholder,
    ],
]);
$claudeCodeCli = 'claude mcp add --transport http myroncms-dev ' . $mcpServerUrl . " \\\n"
    . '  --header "Authorization: Bearer ${MYRON_MCP_TOKEN}"';
$cursorOneTimeMcpJson = $oneTimeToken !== '' ? $mcpJson([
    'url' => $mcpServerUrl,
    'headers' => [
        'Authorization' => 'Bearer ' . $oneTimeToken,
    ],
]) : '';
$antigravityOneTimeMcpJson = $oneTimeToken !== '' ? $mcpJson([
    'serverUrl' => $mcpServerUrl,
    'headers' => [
        'Authorization' => 'Bearer ' . $oneTimeToken,
    ],
]) : '';
$claudeCodeOneTimeCli = $oneTimeToken !== ''
    ? 'claude mcp add --transport http myroncms-dev ' . $mcpServerUrl . " \\\n"
        . '  --header "Authorization: Bearer ' . $oneTimeToken . '"'
    : '';
?>
<div class="mc-page-canvas mc-page-canvas--site">
  <div class="sg-main">

    <section class="sg-section sg-section--callout">
      <h2 class="sg-section__title">Connect Your AI Client</h2>
      <p>Choose the connection method your MCP client supports. Cursor and direct HTTP clients can use Bearer Token. Claude-style remote connectors can use OAuth Connector.</p>
      <dl class="sg-define-list">
        <dt>MCP Server URL</dt>
        <dd>
          <div class="mc-side-sheet__url-row">
            <input class="sg-field__control mc-side-sheet__url-control" type="text" value="<?= myronEscape($mcpServerUrl) ?>" readonly>
            <button class="sg-button" type="button" data-mc-copy-url="<?= myronEscape($mcpServerUrl) ?>">Copy URL</button>
          </div>
          <p class="sg-field__hint">Paste this into a client field named Server URL, Connector URL, or MCP URL. In Claude, this is the main connector URL.</p>
        </dd>
        <dt>Bearer Token Authorization</dt>
        <dd><code>Bearer myr_ai_&lt;token&gt;</code> — create or pick a connection below to get a token.</dd>
        <dt>OAuth Discovery</dt>
        <dd><code><?= myronEscape($oauthProtectedResourceUrl) ?></code></dd>
      </dl>
      <p class="sg-field__hint">Streamable HTTP · MCP protocol <code><?= myronEscape($mcpVersion) ?></code> · For browser-origin clients, add the origin to Site Settings → MCP CORS allow-list.</p>
    </section>

    <?php /* APO-204 Card 3 — token-reveal modal markup appears at end of page (after main content) so the dialog is at document root for clean stacking. The stash unset is moved there too. */ ?>

    <section class="sg-section">
      <h2 class="sg-section__title">IDE JSON Config</h2>
      <p class="sg-field__hint">Use these examples for IDE clients that read MCP configuration from JSON. Replace <code>${MYRON_MCP_TOKEN}</code> with a token from the Bearer Token flow below.</p>
      <dl class="sg-define-list">
        <dt>Cursor / Generic MCP JSON</dt>
        <dd>
          <pre class="sg-code-block"><code><?= myronEscape($cursorMcpJson) ?></code></pre>
          <button class="sg-button" type="button" data-mc-copy-value="<?= myronEscape($cursorMcpJson) ?>">Copy JSON</button>
        </dd>
        <dt>Antigravity MCP JSON</dt>
        <dd>
          <pre class="sg-code-block"><code><?= myronEscape($antigravityMcpJson) ?></code></pre>
          <button class="sg-button" type="button" data-mc-copy-value="<?= myronEscape($antigravityMcpJson) ?>">Copy JSON</button>
        </dd>
        <dt>Claude Code HTTP</dt>
        <dd>
          <pre class="sg-code-block"><code><?= myronEscape($claudeCodeCli) ?></code></pre>
          <button class="sg-button" type="button" data-mc-copy-value="<?= myronEscape($claudeCodeCli) ?>">Copy Command</button>
          <pre class="sg-code-block"><code><?= myronEscape($claudeCodeMcpJson) ?></code></pre>
          <button class="sg-button" type="button" data-mc-copy-value="<?= myronEscape($claudeCodeMcpJson) ?>">Copy JSON</button>
        </dd>
        <dt>Claude Hosted / Desktop Connector</dt>
        <dd>Use the MCP Server URL above with OAuth Connector. Leave OAuth Client ID and OAuth Client Secret blank unless MyronCMS generated those values for a manual connector.</dd>
      </dl>
    </section>

    <section class="sg-section">
      <h2 class="sg-section__title">Bearer Token</h2>
      <p class="sg-field__hint">Use this method for Cursor, direct MCP clients, scripts, and local tools that let you paste an `Authorization: Bearer` token.</p>
      <form method="post" class="sg-form-grid">
        <input type="hidden" name="csrf_token" value="<?= myronEscape($csrfToken) ?>">
        <input type="hidden" name="action" value="create-ai-connection">
        <label class="sg-field"><span class="sg-field__label">Label</span>
          <input class="sg-field__control" type="text" name="label" required maxlength="120" placeholder="Claude desktop @ home">
        </label>
        <label class="sg-field"><span class="sg-field__label">Scope Preset</span>
          <select class="sg-field__control" name="scope_preset">
            <option value="">— Choose scopes manually below —</option>
            <option value="read-everything">Read everything / write nothing</option>
            <option value="author-content">Author content (no admin)</option>
            <option value="full-access">Full access (all 6 scopes)</option>
          </select>
        </label>
        <fieldset class="sg-fieldset">
          <legend class="sg-fieldset__legend">Or pick scopes individually</legend>
          <?php foreach (MyronApiScopes::ALL_BASE as $scope): ?>
          <label class="sg-checkbox"><input type="checkbox" name="scopes[]" value="<?= myronEscape($scope) ?>"> <?= myronEscape($scope) ?></label>
          <?php endforeach; ?>
        </fieldset>
        <label class="sg-field"><span class="sg-field__label">Rate limit (req/min, optional)</span>
          <input class="sg-field__control" type="number" name="requests_per_minute" min="0" max="3600" placeholder="60">
        </label>
        <div class="sg-card__actions">
          <button class="sg-button sg-button--primary" type="submit">Create Connection</button>
        </div>
      </form>
    </section>

    <section class="sg-section">
      <h2 class="sg-section__title">OAuth Connector</h2>
      <p class="sg-field__hint">Use this method for clients that discover OAuth metadata and complete a browser authorization flow, including Claude-compatible remote connectors. Start with the MCP Server URL above. Leave OAuth Client ID and OAuth Client Secret blank unless MyronCMS explicitly generated those values for a manual connector.</p>
      <dl class="sg-define-list">
        <dt>Protected Resource Metadata</dt>
        <dd>
          <div class="mc-side-sheet__url-row">
            <input class="sg-field__control mc-side-sheet__url-control" type="text" value="<?= myronEscape($oauthProtectedResourceUrl) ?>" readonly>
            <button class="sg-button" type="button" data-mc-copy-url="<?= myronEscape($oauthProtectedResourceUrl) ?>">Copy URL</button>
          </div>
          <p class="sg-field__hint">Discovery document for clients and diagnostics. Do not paste this into an OAuth Client ID field.</p>
        </dd>
        <dt>Authorization Server Metadata</dt>
        <dd>
          <div class="mc-side-sheet__url-row">
            <input class="sg-field__control mc-side-sheet__url-control" type="text" value="<?= myronEscape($oauthAuthorizationMetadataUrl) ?>" readonly>
            <button class="sg-button" type="button" data-mc-copy-url="<?= myronEscape($oauthAuthorizationMetadataUrl) ?>">Copy URL</button>
          </div>
          <p class="sg-field__hint">Discovery document for authorization endpoints. Do not paste this into an OAuth Client Secret field.</p>
        </dd>
      </dl>
      <?php if ($oauthGrants === []): ?>
      <p>No active OAuth connector grants.</p>
      <?php else: ?>
      <table class="mc-page-grid">
        <thead><tr><th>Client</th><th>Grant</th><th>Last Used</th><th>Access Expires</th><th>Actions</th></tr></thead>
        <tbody>
        <?php foreach ($oauthGrants as $grant): ?>
        <tr>
          <td><?= myronEscape((string) ($grant['client_name'] ?? 'OAuth MCP Client')) ?></td>
          <td><code><?= myronEscape((string) ($grant['integration_id'] ?? '')) ?></code></td>
          <td><?= myronEscape((string) (($grant['last_used_at'] ?? '') ?: '—')) ?></td>
          <td><?= myronEscape((string) (($grant['access_expires_at'] ?? '') ?: '—')) ?></td>
          <td>
            <form method="post">
              <input type="hidden" name="csrf_token" value="<?= myronEscape($csrfToken) ?>">
              <input type="hidden" name="action" value="revoke-oauth-connection">
              <input type="hidden" name="integration_id" value="<?= myronEscape((string) ($grant['integration_id'] ?? '')) ?>">
              <button class="sg-button sg-button--secondary" type="submit">Revoke</button>
            </form>
          </td>
        </tr>
        <?php endforeach; ?>
        </tbody>
      </table>
      <?php endif; ?>
    </section>

    <section class="sg-section">
      <h2 class="sg-section__title">Bearer Token Connections</h2>
      <?php if ($integrations === []): ?>
      <p>No active AI connections. Create one above to get started.</p>
      <?php else: ?>
      <table class="mc-page-grid">
        <thead><tr><th>Method</th><th>Label</th><th>Token Prefix</th><th>Scopes</th><th>Last Used</th><th>Requests</th><th>Actions</th></tr></thead>
        <tbody>
        <?php foreach ($integrations as $integration): ?>
        <tr>
          <td>Bearer Token</td>
          <td><?= myronEscape($integration->label) ?></td>
          <td><code>myr_ai_<?= myronEscape($integration->tokenPrefix) ?>…</code></td>
          <td><?= myronEscape(implode(', ', $integration->scopes)) ?></td>
          <td><?= myronEscape((string) ($integration->lastUsedAt ?? '—')) ?></td>
          <td><?= (int) $integration->requestCount ?></td>
          <td>
            <a class="sg-button sg-button--secondary" href="<?= myronEscape(myronBuildAdminUrl('administration.aiConnections', ['manage' => $integration->id])) ?>">Manage</a>
          </td>
        </tr>
        <?php endforeach; ?>
        </tbody>
      </table>
      <?php endif; ?>
    </section>

    <?php if (is_array($managed)): ?>
    <?php /* $managed is MyronAiIntegration — keep block compatible */ ?>
    <?php endif; ?>
    <?php if ($managed !== null && !$showToken): ?>
    <section class="sg-section">
      <h2 class="sg-section__title">Manage: <?= myronEscape($managed->label) ?></h2>
      <p>Token prefix: <code>myr_ai_<?= myronEscape($managed->tokenPrefix) ?>…</code></p>
      <p>Created: <?= myronEscape($managed->createdAt) ?> · Last used: <?= myronEscape((string) ($managed->lastUsedAt ?? '—')) ?> · Requests: <?= (int) $managed->requestCount ?></p>
      <p>Scopes: <?= myronEscape(implode(', ', $managed->scopes)) ?></p>
      <p>Status: <strong><?= $managed->isRevoked() ? 'revoked' : 'active' ?></strong> <?= $managed->isRevoked() ? '(revoked at ' . myronEscape((string) $managed->revokedAt) . ')' : '' ?></p>

      <?php if (!$managed->isRevoked()): ?>
      <h3 class="sg-section__subtitle">Update</h3>
      <form method="post" class="sg-form-grid">
        <input type="hidden" name="csrf_token" value="<?= myronEscape($csrfToken) ?>">
        <input type="hidden" name="action" value="update-ai-connection">
        <input type="hidden" name="integration_id" value="<?= myronEscape($managed->id) ?>">
        <label class="sg-field"><span class="sg-field__label">Label</span>
          <input class="sg-field__control" type="text" name="label" value="<?= myronEscape($managed->label) ?>" required>
        </label>
        <fieldset class="sg-fieldset">
          <legend class="sg-fieldset__legend">Scopes</legend>
          <?php foreach (MyronApiScopes::ALL_BASE as $scope): ?>
          <label class="sg-checkbox"><input type="checkbox" name="scopes[]" value="<?= myronEscape($scope) ?>" <?= in_array($scope, $managed->scopes, true) ? 'checked' : '' ?>> <?= myronEscape($scope) ?></label>
          <?php endforeach; ?>
        </fieldset>
        <div class="sg-card__actions">
          <button class="sg-button sg-button--primary" type="submit">Save Changes</button>
        </div>
      </form>

      <h3 class="sg-section__subtitle">Rotate Token</h3>
      <p class="sg-field__hint">Generates a new token; the existing token immediately stops working. Use for compromised-token recovery without re-creating the integration record.</p>
      <form method="post" id="ai-rotate-form">
        <input type="hidden" name="csrf_token" value="<?= myronEscape($csrfToken) ?>">
        <input type="hidden" name="action" value="rotate-ai-connection">
        <input type="hidden" name="integration_id" value="<?= myronEscape($managed->id) ?>">
        <button class="sg-button sg-button--secondary" type="button" data-ai-rotate-trigger>Rotate Token</button>
      </form>

      <h3 class="sg-section__subtitle">Revoke</h3>
      <p class="sg-field__hint">Permanently disables the token. The integration record is preserved for audit history.</p>
      <form method="post" class="mc-side-sheet__danger" id="ai-revoke-form">
        <input type="hidden" name="csrf_token" value="<?= myronEscape($csrfToken) ?>">
        <input type="hidden" name="action" value="revoke-ai-connection">
        <input type="hidden" name="integration_id" value="<?= myronEscape($managed->id) ?>">
        <button class="sg-button sg-button--danger" type="button" data-ai-revoke-trigger>Revoke Connection</button>
      </form>
      <?php endif; ?>
    </section>
    <?php endif; ?>
  </div>
</div>

<?php if ($managed !== null && !$managed->isRevoked()): ?>
<?php /* APO-204 Card 4 — Revoke + Rotate confirmation modals.
   These replace the previous browser-native confirmation prompts.
   Backdrop click DOES dismiss these (safe for confirmations; different
   from the token-reveal modal which suppresses backdrop dismiss). */ ?>
<div class="myron-dialog-backdrop myron-dialog-backdrop--ai-revoke-confirm" hidden aria-hidden="true" data-ai-revoke-backdrop></div>
<div class="myron-dialog myron-dialog--confirmation myron-dialog--ai-revoke-confirm" id="ai-revoke-confirm-dialog" role="dialog" aria-modal="true" aria-hidden="true" hidden tabindex="-1" data-ai-revoke-dialog aria-labelledby="ai-revoke-confirm-title">
  <div class="myron-dialog__header">
    <div>
      <h3 class="myron-heading" id="ai-revoke-confirm-title" data-level="4">Revoke this AI connection?</h3>
      <p class="myron-paragraph myron-paragraph--muted">The token will stop working immediately. The integration record stays in the audit log for history. This cannot be undone.</p>
    </div>
    <button class="myron-dialog__close" type="button" aria-label="Close revoke confirmation" data-ai-revoke-close>&times;</button>
  </div>
  <div class="myron-dialog__body">
    <p class="myron-paragraph">Revoke <strong><?= myronEscape($managed->label) ?></strong>?</p>
  </div>
  <div class="myron-dialog__actions">
    <button class="myron-button myron-button--intent-neutral myron-button--appearance-outline myron-button--size-md" type="button" data-ai-revoke-close>Cancel</button>
    <button class="myron-button myron-button--intent-danger myron-button--appearance-solid myron-button--size-md" type="submit" form="ai-revoke-form">Revoke Connection</button>
  </div>
</div>

<div class="myron-dialog-backdrop myron-dialog-backdrop--ai-rotate-confirm" hidden aria-hidden="true" data-ai-rotate-backdrop></div>
<div class="myron-dialog myron-dialog--confirmation myron-dialog--ai-rotate-confirm" id="ai-rotate-confirm-dialog" role="dialog" aria-modal="true" aria-hidden="true" hidden tabindex="-1" data-ai-rotate-dialog aria-labelledby="ai-rotate-confirm-title">
  <div class="myron-dialog__header">
    <div>
      <h3 class="myron-heading" id="ai-rotate-confirm-title" data-level="4">Rotate this token?</h3>
      <p class="myron-paragraph myron-paragraph--muted">The current token stops working immediately. A new token will be shown once on the next screen.</p>
    </div>
    <button class="myron-dialog__close" type="button" aria-label="Close rotate confirmation" data-ai-rotate-close>&times;</button>
  </div>
  <div class="myron-dialog__body">
    <p class="myron-paragraph">Rotate the token for <strong><?= myronEscape($managed->label) ?></strong>?</p>
  </div>
  <div class="myron-dialog__actions">
    <button class="myron-button myron-button--intent-neutral myron-button--appearance-outline myron-button--size-md" type="button" data-ai-rotate-close>Cancel</button>
    <button class="myron-button myron-button--intent-primary myron-button--appearance-solid myron-button--size-md" type="submit" form="ai-rotate-form">Rotate Token</button>
  </div>
</div>
<?php endif; ?>

<?php if ($oneTimeToken !== ''): ?>
<?php /* APO-204 Card 3 — One-time-token reveal modal (myron-dialog).
   Auto-opens via the per-page <script> below. Backdrop click does NOT
   dismiss (one-time-secret safety). X-button + Esc + Done button do.
   The 'Token Copied' confirmation persists; modal stays open until
   the operator dismisses. After this render, the session-stash entry
   is cleared so refresh hides the token. */ ?>
<div class="myron-dialog-backdrop myron-dialog-backdrop--ai-token-reveal" hidden aria-hidden="true" data-ai-token-backdrop></div>
<div class="myron-dialog myron-dialog--ai-token-reveal" id="ai-token-reveal-dialog" role="dialog" aria-modal="true" aria-hidden="true" hidden tabindex="-1" data-ai-token-dialog aria-labelledby="ai-token-reveal-title">
  <div class="myron-dialog__header">
    <div>
      <h3 class="myron-heading" id="ai-token-reveal-title" data-level="4">AI Connection Token — Copy Now</h3>
      <p class="myron-paragraph myron-paragraph--muted">This token is shown only once. Copy it into your MCP client config before closing — refreshing this page will hide it.</p>
    </div>
    <button class="myron-dialog__close" type="button" aria-label="Close token reveal" data-ai-token-close>&times;</button>
  </div>
  <div class="myron-dialog__body">
    <div class="mc-side-sheet__url-row">
      <input class="sg-field__control mc-side-sheet__url-control" type="text" value="<?= myronEscape($oneTimeToken) ?>" readonly aria-label="Generated AI integration token" data-ai-token-input>
      <button class="sg-button sg-button--primary" type="button"
              data-mc-copy-value="<?= myronEscape($oneTimeToken) ?>"
              data-mc-copy-feedback="persistent"
              data-ai-token-copy>Copy Token</button>
    </div>
    <span class="sg-status sg-status--success" hidden aria-hidden="true" data-mc-copy-status></span>
    <p class="sg-field__hint">Use this in your client config: <code>Authorization: Bearer &lt;token&gt;</code></p>
    <h4 class="sg-section__subtitle">Full Config Snippets</h4>
    <p class="sg-field__hint">These snippets include the generated token and are shown only in this one-time reveal.</p>
    <dl class="sg-define-list">
      <dt>Cursor / Generic MCP JSON</dt>
      <dd>
        <pre class="sg-code-block"><code><?= myronEscape($cursorOneTimeMcpJson) ?></code></pre>
        <button class="sg-button" type="button" data-mc-copy-value="<?= myronEscape($cursorOneTimeMcpJson) ?>">Copy JSON</button>
      </dd>
      <dt>Antigravity MCP JSON</dt>
      <dd>
        <pre class="sg-code-block"><code><?= myronEscape($antigravityOneTimeMcpJson) ?></code></pre>
        <button class="sg-button" type="button" data-mc-copy-value="<?= myronEscape($antigravityOneTimeMcpJson) ?>">Copy JSON</button>
      </dd>
      <dt>Claude Code HTTP</dt>
      <dd>
        <pre class="sg-code-block"><code><?= myronEscape($claudeCodeOneTimeCli) ?></code></pre>
        <button class="sg-button" type="button" data-mc-copy-value="<?= myronEscape($claudeCodeOneTimeCli) ?>">Copy Command</button>
      </dd>
    </dl>
  </div>
  <div class="myron-dialog__actions">
    <button class="myron-button myron-button--intent-neutral myron-button--appearance-outline myron-button--size-md" type="button" data-ai-token-close>Done</button>
  </div>
</div>
<?php
// After rendering the token into the dialog, remove from stash so refresh hides it.
unset($stashedTokens[$managed->id]);
$_SESSION['myron_ai_one_time_tokens'] = $stashedTokens;
?>
<?php endif; ?>

<?php /* APO-204 — Per-page dialog wiring for the AI Connections surface.
   Inlined because the admin shell does not currently host a generic
   myronDialogController() utility; the page-editor pattern is the
   reference implementation. Cards 3 + 4 share this <script> block.
   Auto-open of the token reveal dialog runs on DOMContentLoaded if the
   server rendered the dialog (one-time token present in stash). */ ?>
<script>
(function aiConnectionsDialogs(){
  "use strict";
  function ready(fn){ if(document.readyState==="loading"){ document.addEventListener("DOMContentLoaded",fn,{once:true}); } else { fn(); } }

  function getFocusable(container){
    return Array.from(container.querySelectorAll(
      'a[href],button:not([disabled]),input:not([disabled]):not([type="hidden"]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
    )).filter(function(el){ return el.offsetParent !== null || el === document.activeElement; });
  }

  function openDialog(dialog, options){
    options = options || {};
    var backdrop = options.backdrop || null;
    var dismissOnBackdrop = options.dismissOnBackdrop !== false; // default true
    var prevFocus = document.activeElement;

    dialog.removeAttribute("hidden");
    dialog.setAttribute("aria-hidden","false");
    if (backdrop){ backdrop.removeAttribute("hidden"); backdrop.setAttribute("aria-hidden","false"); }

    var first = getFocusable(dialog)[0] || dialog;
    setTimeout(function(){ first.focus(); }, 0);

    function close(){
      dialog.setAttribute("hidden","");
      dialog.setAttribute("aria-hidden","true");
      if (backdrop){ backdrop.setAttribute("hidden",""); backdrop.setAttribute("aria-hidden","true"); }
      document.removeEventListener("keydown", onKey, true);
      if (backdrop && dismissOnBackdrop){ backdrop.removeEventListener("click", onBackdrop); }
      dialog.querySelectorAll("[data-mc-copy-status]").forEach(function(s){ s.setAttribute("hidden",""); s.setAttribute("aria-hidden","true"); s.textContent=""; });
      if (prevFocus && typeof prevFocus.focus === "function"){ prevFocus.focus(); }
      if (typeof options.onClose === "function"){ options.onClose(); }
    }

    function onKey(e){
      if (e.key === "Escape"){ e.preventDefault(); close(); return; }
      if (e.key !== "Tab") return;
      var focusables = getFocusable(dialog);
      if (focusables.length === 0) return;
      var first = focusables[0], last = focusables[focusables.length-1];
      if (e.shiftKey && document.activeElement === first){ e.preventDefault(); last.focus(); }
      else if (!e.shiftKey && document.activeElement === last){ e.preventDefault(); first.focus(); }
    }
    document.addEventListener("keydown", onKey, true);

    function onBackdrop(){ close(); }
    if (backdrop && dismissOnBackdrop){ backdrop.addEventListener("click", onBackdrop); }

    dialog.querySelectorAll(options.closeSelector || "[data-ai-token-close]").forEach(function(btn){
      btn.addEventListener("click", function(){ close(); }, { once: false });
    });
    return { close: close };
  }
  window.__myronAiOpenDialog = openDialog; // exposed for Card 4 reuse

  function wireConfirmTrigger(triggerSelector, dialogSelector, backdropSelector, closeSelector){
    var triggers = document.querySelectorAll(triggerSelector);
    triggers.forEach(function(btn){
      btn.addEventListener("click", function(){
        var dialog = document.querySelector(dialogSelector);
        var backdrop = document.querySelector(backdropSelector);
        if (!dialog) return;
        openDialog(dialog, { backdrop: backdrop, dismissOnBackdrop: true, closeSelector: closeSelector });
      });
    });
  }

  ready(function(){
    var tokenDialog = document.querySelector("[data-ai-token-dialog]");
    if (tokenDialog){
      var backdrop = document.querySelector("[data-ai-token-backdrop]");
      openDialog(tokenDialog, { backdrop: backdrop, dismissOnBackdrop: false, closeSelector: "[data-ai-token-close]" });
    }
    // APO-204 Card 4 — revoke + rotate confirmation triggers.
    wireConfirmTrigger("[data-ai-revoke-trigger]", "[data-ai-revoke-dialog]", "[data-ai-revoke-backdrop]", "[data-ai-revoke-close]");
    wireConfirmTrigger("[data-ai-rotate-trigger]", "[data-ai-rotate-dialog]", "[data-ai-rotate-backdrop]", "[data-ai-rotate-close]");
  });
})();
</script>

<?php myronRenderAdminShellEnd(); ?>
