/* ═══════════════════════════════════════════════════════════════════
   PluginConnect.jsx — Plugin Connect approval page
   ═══════════════════════════════════════════════════════════════════

   Renders the "Allow this WordPress site to connect?" UI for the
   OAuth-style plugin connection flow. Per spec §13 Q2 (locked
   2026-05-20): always show the prompt — no auto-approve shortcut.

   Used in two hosts:
     - `plugin-connect.html` (standalone, popup-friendly, faster load)
     - `App.jsx ?plugin_connect=1` tab (fallback when popup blocked)
   The same component runs in both; the host shell is the only difference.

   URL params expected:
     site_url      — the WordPress site the plugin is on (origin must match callback_url)
     install_id    — plugin's local install identifier (UUID-ish)
     state         — CSRF-prevention nonce echoed back to plugin
     callback_url  — where to redirect with the new key (must be same origin as site_url)

   Flow:
     1. Parse + validate params (origin match on site_url ↔ callback_url)
     2. If not authenticated: show Sign in CTA (login.wpsitebeam.io with return_to)
     3. Show "Allow connection?" UI with site_url echoed prominently
     4. On Approve: POST /account/api-keys (key_type='per_install'),
        redirect popup to callback_url?api_key=…&state=…
     5. On Cancel: redirect popup to callback_url?error=user_cancelled&state=…

   Security model:
     - state nonce is opaque to the SaaS — plugin generates + verifies it
     - callback_url origin MUST match site_url origin (prevents redirect-abuse)
     - api_key returned in URL fragment is single-use (plugin extracts immediately)
     - if user cancels or errors out, no key is created

   Spec: _PLUGIN-API-KEY-AUTH-SPEC.md §8, Q2 (always prompt) + Q4 (both hosts)
   ═══════════════════════════════════════════════════════════════════ */

(function () {
  'use strict';
  const { useState, useEffect, useMemo } = React;

  const RAILWAY_URL = (window.WPSB_CONFIG && window.WPSB_CONFIG.RAILWAY_URL)
    || 'https://wpsitebeam-railway-api-production.up.railway.app';
  const LOGIN_URL = 'https://login.wpsitebeam.io/login';

  /* ── Helpers ─────────────────────────────────────────────────────── */

  function parseUrlParams() {
    const sp = new URLSearchParams(window.location.search);
    return {
      site_url:     sp.get('site_url')     || '',
      install_id:   sp.get('install_id')   || '',
      state:        sp.get('state')        || '',
      callback_url: sp.get('callback_url') || '',
    };
  }

  function validateParams(p) {
    const missing = [];
    if (!p.site_url)     missing.push('site_url');
    if (!p.install_id)   missing.push('install_id');
    if (!p.state)        missing.push('state');
    if (!p.callback_url) missing.push('callback_url');
    if (missing.length) {
      return { ok: false, code: 'MISSING_PARAMS', detail: `Missing: ${missing.join(', ')}` };
    }
    // Both URLs must parse
    let siteOrigin, cbOrigin;
    try { siteOrigin = new URL(p.site_url).origin;     } catch (e) { return { ok: false, code: 'BAD_SITE_URL',     detail: 'site_url is not a valid URL' }; }
    try { cbOrigin   = new URL(p.callback_url).origin; } catch (e) { return { ok: false, code: 'BAD_CALLBACK_URL', detail: 'callback_url is not a valid URL' }; }
    // Origin must match (anti-phishing — prevents popup from redirecting to attacker site)
    if (siteOrigin !== cbOrigin) {
      return { ok: false, code: 'ORIGIN_MISMATCH', detail: `callback_url origin (${cbOrigin}) does not match site_url origin (${siteOrigin})` };
    }
    // callback_url must be https (or http for localhost dev convenience)
    const isHttps   = cbOrigin.startsWith('https://');
    const isLocalDev = /^http:\/\/(localhost|127\.0\.0\.1|10\.|192\.168\.)/.test(cbOrigin);
    if (!isHttps && !isLocalDev) {
      return { ok: false, code: 'INSECURE_CALLBACK', detail: 'callback_url must use HTTPS' };
    }
    return { ok: true };
  }

  function buildCallbackUrl(baseCallback, paramObj) {
    try {
      const u = new URL(baseCallback);
      for (const [k, v] of Object.entries(paramObj)) {
        if (v != null) u.searchParams.set(k, v);
      }
      return u.toString();
    } catch (e) {
      return null;
    }
  }

  /* ── Styles (use CSS vars per design standards §6.5.2) ──────────── */
  const card = {
    background: 'var(--surface)',
    border: '1px solid var(--border)',
    borderRadius: 12,
    padding: 'clamp(20px, 4vw, 36px)',
    maxWidth: 520,
    width: '100%',
    boxShadow: '0 12px 40px rgba(0,0,0,.4)',
  };
  const container = {
    minHeight: '100vh',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
    background: 'var(--bg)',
  };
  const labelStyle = { fontSize: '.78rem', fontWeight: 600, color: 'var(--dim)',
                       textTransform: 'uppercase', letterSpacing: '.06em', marginBottom: 6 };
  const valueStyle = { fontSize: '.95rem', color: 'var(--text)', wordBreak: 'break-all',
                       padding: '10px 12px', background: 'var(--surface-2)',
                       border: '1px solid var(--border)', borderRadius: 6, fontFamily: 'monospace' };

  /* ── Component ───────────────────────────────────────────────────── */

  function PluginConnect() {
    const [params, setParams]   = useState(null);
    const [paramErr, setParamErr] = useState(null);
    const [step, setStep]       = useState('parsing');
    const [errMsg, setErrMsg]   = useState('');
    const [authChecked, setAuthChecked] = useState(false);

    // 1. Parse + validate URL params (once on mount)
    useEffect(() => {
      const p = parseUrlParams();
      const v = validateParams(p);
      if (!v.ok) {
        setParamErr(v);
        setStep('invalid_params');
        return;
      }
      setParams(p);
      setStep('checking_auth');
    }, []);

    // 2. Check auth state once params are valid
    useEffect(() => {
      if (step !== 'checking_auth') return;
      let cancelled = false;
      // Wait for window.currentUser to be populated by the host's auth flow.
      // Standalone host: supabase init + authClient sets it during boot.
      // App.jsx host:    index.html's main() sets it before App.jsx mounts.
      const tick = (attempt) => {
        if (cancelled) return;
        if (typeof window.currentUser !== 'undefined') {
          setAuthChecked(true);
          if (window.currentUser) {
            setStep('awaiting_approval');
          } else {
            setStep('awaiting_login');
          }
          return;
        }
        // Up to ~6s of polling for slow boot
        if (attempt < 30) {
          setTimeout(() => tick(attempt + 1), 200);
        } else {
          // Auth never resolved — assume no session
          setAuthChecked(true);
          setStep('awaiting_login');
        }
      };
      tick(0);
      return () => { cancelled = true; };
    }, [step]);

    // Shared completion: build the callback URL with the plain key and
    // redirect the popup back to the plugin. Used by both the normal mint
    // path and the "key already exists -> rotate" self-heal path.
    function completeWithKey(plainKey) {
      const cbUrl = buildCallbackUrl(params.callback_url, {
        api_key: plainKey,
        state:   params.state,
      });
      if (!cbUrl) {
        setErrMsg('Could not construct callback URL. Please copy the key manually and paste into the plugin.');
        setStep('error');
        return;
      }
      setStep('success');
      // Brief flash of success before the popup navigates back to the plugin.
      setTimeout(() => { window.location.href = cbUrl; }, 600);
    }

    // 3. Approve handler — calls POST /account/api-keys then redirects to callback
    async function handleApprove() {
      if (!params) return;
      setStep('approving');
      try {
        // v3.1.3 fix: send the raw Supabase access token directly, NOT an
        // exchanged token. /auth/exchange signs HS256 (SUPABASE_JWT_SECRET)
        // while the API now verifies ES256 via the Supabase JWKS, so every
        // exchanged token fails verification ("Invalid token"). The raw
        // Supabase token IS ES256, and /account/api-keys accepts the
        // 'authenticated' audience and re-derives role from the DB — so it
        // authenticates correctly with no exchange step.
        let supaToken = null;
        try {
          if (window.supabase_client && window.supabase_client.auth) {
            const { data } = await window.supabase_client.auth.getSession();
            supaToken = (data && data.session && data.session.access_token) || null;
          }
        } catch (_) { /* fall through to currentToken */ }
        if (!supaToken && window.currentToken) supaToken = window.currentToken;
        if (!supaToken) {
          setErrMsg('You are not signed in to WPSiteBeam in this window. Sign in, then click Approve & connect.');
          setStep('error');
          return;
        }
        const headers = { 'Authorization': 'Bearer ' + supaToken };
        const r = await fetch(RAILWAY_URL + '/account/api-keys', {
          method: 'POST',
          headers: Object.assign({ 'Content-Type': 'application/json' }, headers),
          body: JSON.stringify({
            key_type:   'per_install',
            site_url:   params.site_url,
            install_id: params.install_id,
            label:      'Plugin · ' + params.site_url,
            mode:       'live',
          }),
        });
        const data = await r.json().catch(() => ({}));
        if (!r.ok) {
          // Friendly handling for the known cases
          const code = data && data.code;
          if (code === 'KEY_ALREADY_EXISTS' && data.existing_id) {
            // Self-heal: an active key already exists for this install — almost
            // always a prior attempt whose callback didn't finish storing the
            // key on the WordPress side. The old key's plaintext can't be
            // re-read (only its hash is stored), so we rotate the existing key
            // in place and reconnect with the freshly minted plain key. This
            // turns the old dead-end ("already exists → Try again" looping
            // forever) into a one-shot recovery.
            try {
              const rg = await fetch(RAILWAY_URL + '/account/api-keys/' + encodeURIComponent(data.existing_id) + '/regenerate', {
                method: 'POST',
                headers: Object.assign({ 'Content-Type': 'application/json' }, headers),
                body: JSON.stringify({}),
              });
              const rgData = await rg.json().catch(() => ({}));
              if (rg.ok && rgData.plain_key) {
                completeWithKey(rgData.plain_key);
                return;
              }
              setErrMsg('A key already exists for this install and automatic rotation failed (' +
                        (rgData.code || ('HTTP ' + rg.status)) + '). Open Account → API Keys, rotate the key ' +
                        'for this site, then paste it here under “Other ways to connect.”');
            } catch (e2) {
              setErrMsg('A key already exists and rotation failed: ' + (e2.message || 'network error') +
                        '. Paste a key manually under “Other ways to connect.”');
            }
            setStep('error');
            return;
          } else if (code === 'KEY_ALREADY_EXISTS') {
            setErrMsg('An active API key already exists for this install. Rotate it in Account → API Keys, then paste the new key under “Other ways to connect.”');
          } else if (code === 'MASTER_KEY_NO_BINDING') {
            setErrMsg('Server rejected a per-install request as a Master key request. This is a bug — please contact support.');
          } else {
            setErrMsg(data.error || ('Request failed with HTTP ' + r.status));
          }
          setStep('error');
          return;
        }

        if (!data.plain_key) {
          setErrMsg('Server returned no key. Please try again.');
          setStep('error');
          return;
        }

        // 4. Redirect popup back to plugin with key + state
        completeWithKey(data.plain_key);
      } catch (e) {
        setErrMsg(e.message || 'Network error. Check your connection and try again.');
        setStep('error');
      }
    }

    // 5. Cancel handler
    function handleCancel() {
      if (!params) {
        // No callback to return to — just close (popup) or go home
        if (window.opener) window.close();
        else window.location.href = 'https://app.wpsitebeam.io/';
        return;
      }
      const cbUrl = buildCallbackUrl(params.callback_url, {
        error: 'user_cancelled',
        state: params.state,
      });
      if (cbUrl) {
        window.location.href = cbUrl;
      } else {
        if (window.opener) window.close();
        else window.location.href = 'https://app.wpsitebeam.io/';
      }
    }

    function handleSignIn() {
      // Send user to login with return_to back to this URL (preserves query params).
      const returnTo = encodeURIComponent(window.location.href);
      window.location.href = LOGIN_URL + '?return_to=' + returnTo;
    }

    /* ── Render ────────────────────────────────────────────────────── */

    const userEmail = (window.currentUser && window.currentUser.email) || '';

    // ── State: invalid params ──
    if (step === 'invalid_params') {
      return (
        <div style={container}>
          <div style={card} role="alert" aria-labelledby="pc-title">
            <h1 id="pc-title" style={{ fontSize: '1.4rem', margin: '0 0 12px', color: 'var(--red)' }}>
              Invalid connection link
            </h1>
            <p style={{ color: 'var(--text)', marginBottom: 8 }}>
              This connection link is missing or malformed:
            </p>
            <p style={{ color: 'var(--dim)', fontSize: '.85rem', fontFamily: 'monospace',
                         background: 'var(--surface-2)', padding: '10px 12px', borderRadius: 6,
                         border: '1px solid var(--border)' }}>
              {paramErr ? paramErr.detail : 'Unknown error'}
            </p>
            <p style={{ color: 'var(--dim)', fontSize: '.85rem', marginTop: 16, lineHeight: 1.6 }}>
              Close this window and try the Connect button again from your plugin.
              If this keeps happening, please contact support.
            </p>
            <div style={{ marginTop: 20, display: 'flex', justifyContent: 'flex-end' }}>
              <button className="btn btn-ghost" onClick={handleCancel}>Close</button>
            </div>
          </div>
        </div>
      );
    }

    // ── State: parsing / checking_auth ──
    if (step === 'parsing' || step === 'checking_auth') {
      return (
        <div style={container}>
          <div style={card} aria-live="polite">
            <p style={{ color: 'var(--dim)', textAlign: 'center', padding: '20px 0' }}>
              {step === 'parsing' ? 'Loading…' : 'Checking your session…'}
            </p>
          </div>
        </div>
      );
    }

    // ── State: awaiting_login ──
    if (step === 'awaiting_login') {
      return (
        <div style={container}>
          <div style={card} aria-labelledby="pc-title">
            <h1 id="pc-title" style={{ fontSize: '1.4rem', margin: '0 0 8px', color: 'var(--text)' }}>
              Sign in to continue
            </h1>
            <p style={{ color: 'var(--dim)', marginBottom: 20, fontSize: '.92rem', lineHeight: 1.6 }}>
              The WordPress plugin at <code style={{ color: 'var(--beam)', fontFamily: 'monospace' }}>{params && params.site_url}</code> is
              requesting permission to connect to your WPSiteBeam account. Sign in first to approve.
            </p>
            <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
              <button className="btn btn-ghost" onClick={handleCancel}>
                Cancel
              </button>
              <button className="btn btn-primary" onClick={handleSignIn} autoFocus>
                Sign in
              </button>
            </div>
          </div>
        </div>
      );
    }

    // ── State: awaiting_approval ──
    if (step === 'awaiting_approval') {
      return (
        <div style={container}>
          <div style={card} aria-labelledby="pc-title">
            <h1 id="pc-title" style={{ fontSize: '1.4rem', margin: '0 0 8px', color: 'var(--text)' }}>
              Approve plugin connection
            </h1>
            <p style={{ color: 'var(--dim)', marginBottom: 20, fontSize: '.92rem', lineHeight: 1.6 }}>
              The WPSiteBeam WordPress plugin is requesting permission to connect to your account.
              Approving generates a new per-install API key bound to the site below.
            </p>

            <div style={{ marginBottom: 14 }}>
              <div style={labelStyle}>Site</div>
              <div style={valueStyle}>{params.site_url}</div>
            </div>
            <div style={{ marginBottom: 14 }}>
              <div style={labelStyle}>Connecting as</div>
              <div style={valueStyle}>{userEmail || '(signed in)'}</div>
            </div>
            <div style={{ marginBottom: 14 }}>
              <div style={labelStyle}>Install ID</div>
              <div style={Object.assign({}, valueStyle, { fontSize: '.78rem' })}>{params.install_id}</div>
            </div>

            <div style={{ padding: '12px 14px', background: 'var(--surface-2)', border: '1px solid var(--border)',
                          borderRadius: 8, fontSize: '.82rem', color: 'var(--dim)', marginTop: 14, lineHeight: 1.55 }}>
              <strong style={{ color: 'var(--text)' }}>What this allows:</strong> the plugin can pull queued
              operations, report results, push site telemetry, and serve partner offers. You can revoke
              the key any time from Account → API Keys.
            </div>

            <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 22 }}>
              <button className="btn btn-ghost" onClick={handleCancel}>
                Cancel
              </button>
              <button className="btn btn-primary" onClick={handleApprove} autoFocus>
                Approve &amp; connect
              </button>
            </div>
          </div>
        </div>
      );
    }

    // ── State: approving (loading) ──
    if (step === 'approving') {
      return (
        <div style={container}>
          <div style={card} aria-live="polite">
            <p style={{ color: 'var(--text)', textAlign: 'center', padding: '20px 0', fontSize: '.95rem' }}>
              Generating API key…
            </p>
          </div>
        </div>
      );
    }

    // ── State: success ──
    if (step === 'success') {
      return (
        <div style={container}>
          <div style={card} aria-live="polite" aria-labelledby="pc-title">
            <h1 id="pc-title" style={{ fontSize: '1.3rem', margin: '0 0 12px', color: 'var(--green)' }}>
              ✓ Connected
            </h1>
            <p style={{ color: 'var(--text)', marginBottom: 8, lineHeight: 1.55 }}>
              Returning to your WordPress site…
            </p>
            <p style={{ color: 'var(--dim)', fontSize: '.82rem' }}>
              If this window doesn&apos;t close automatically, you can close it now.
            </p>
          </div>
        </div>
      );
    }

    // ── State: error ──
    if (step === 'error') {
      return (
        <div style={container}>
          <div style={card} role="alert" aria-labelledby="pc-title">
            <h1 id="pc-title" style={{ fontSize: '1.3rem', margin: '0 0 12px', color: 'var(--red)' }}>
              Connection failed
            </h1>
            <p style={{ color: 'var(--text)', marginBottom: 16, lineHeight: 1.55 }}>
              {errMsg || 'An unexpected error occurred.'}
            </p>
            <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
              <button className="btn btn-ghost" onClick={handleCancel}>Cancel</button>
              <button className="btn btn-primary" onClick={() => setStep('awaiting_approval')}>
                Try again
              </button>
            </div>
          </div>
        </div>
      );
    }

    // Fallback (shouldn't reach here)
    return null;
  }

  window.PluginConnect = PluginConnect;
})();
