/* auth.jsx — SpacetimeAuth OIDC session + gated app shell */

const SPACETIME_AUTH_DEFAULTS = {
  enabled: true,
  authority: 'https://auth.spacetimedb.com/oidc',
  clientId: '',
  scope: 'openid profile email',
  responseType: 'code',
  redirectUri: `${window.location.origin}/`,
  postLogoutRedirectUri: `${window.location.origin}/`,
  automaticSilentRenew: true,
  allowedEmails: [],
  requireEmailVerified: true,
};

let _currentAuthUser = null;
let _currentAuthConfig = SPACETIME_AUTH_DEFAULTS;

const AuthContext = React.createContext({
  enabled: true,
  configured: false,
  isLoading: true,
  isAuthenticated: false,
  user: null,
  email: '',
  error: null,
  activeNavigator: null,
  signinRedirect: async () => {},
  signoutRedirect: async () => {},
  removeUser: async () => {},
});

function normalizeEmail(email) {
  return String(email || '').trim().toLowerCase();
}

function normalizeEmailList(value) {
  const list = Array.isArray(value)
    ? value
    : String(value || '').split(/[\s,;]+/);
  return list.map(normalizeEmail).filter(Boolean);
}

function authUserEmail(user) {
  return normalizeEmail(user && user.profile && user.profile.email);
}

function maskEmailSegment(segment) {
  const value = String(segment || '');
  if (!value) return '*';
  return value.charAt(0) + '*'.repeat(Math.min(3, Math.max(2, value.length - 1)));
}

function obfuscateEmail(email) {
  const value = normalizeEmail(email);
  if (!value) return '';
  const at = value.indexOf('@');
  if (at === -1) return maskEmailSegment(value);

  const local = value.slice(0, at);
  const domain = value.slice(at + 1);
  const domainParts = domain.split('.').filter(Boolean);
  const suffix = domainParts.length > 1 ? domainParts.pop() : '';
  const domainName = domainParts.join('.') || domain;
  const maskedDomain = suffix ? `${maskEmailSegment(domainName)}.${suffix}` : maskEmailSegment(domainName);

  return `${maskEmailSegment(local)}@${maskedDomain}`;
}

function EmailText({ email, reveal = false, className = '' }) {
  const fullEmail = normalizeEmail(email);
  if (!fullEmail) return null;
  const displayEmail = reveal ? fullEmail : obfuscateEmail(fullEmail);
  return (
    <span className={className} title={displayEmail} aria-label={displayEmail}>
      {displayEmail}
    </span>
  );
}

function isPlaceholderClientId(clientId) {
  const id = String(clientId || '').trim();
  return !id || id === 'YOUR_CLIENT_ID' || id === 'YOUR_SPACETIMEAUTH_CLIENT_ID';
}

function mergeAuthConfig(base, next) {
  const out = { ...base };
  Object.entries(next || {}).forEach(([key, value]) => {
    if (value === undefined || value === null) return;
    if (typeof value === 'string' && value.trim() === '') return;
    out[key] = value;
  });
  out.clientId = out.clientId || out.client_id || '';
  out.client_id = out.clientId;
  out.redirectUri = out.redirectUri || out.redirect_uri || `${window.location.origin}/`;
  out.redirect_uri = out.redirectUri;
  out.postLogoutRedirectUri = out.postLogoutRedirectUri || out.post_logout_redirect_uri || `${window.location.origin}/`;
  out.post_logout_redirect_uri = out.postLogoutRedirectUri;
  out.responseType = out.responseType || out.response_type || 'code';
  out.response_type = out.responseType;
  out.allowedEmails = normalizeEmailList(out.allowedEmails);
  return out;
}

async function loadSpacetimeAuthConfig() {
  let config = mergeAuthConfig(SPACETIME_AUTH_DEFAULTS, window.SLOWCOMPACT_AUTH || {});
  try {
    const res = await fetch('/auth/config', { cache: 'no-store' });
    if (res.ok) {
      config = mergeAuthConfig(config, await res.json());
    }
  } catch {}
  _currentAuthConfig = config;
  return config;
}

function hasOidcCallbackParams() {
  const params = new URLSearchParams(window.location.search);
  const keys = Array.from(params.keys());
  return Boolean(params.get('state') && (params.get('code') || params.get('error') || keys.every(key => key === 'state')));
}

function cleanAuthUrl(user) {
  const returnHash = user && user.state && user.state.returnHash;
  const hash = typeof returnHash === 'string' && returnHash.startsWith('#') ? returnHash : '#/';
  window.history.replaceState({}, document.title, `${window.location.pathname}${hash}`);
}

function createUserManager(config) {
  if (!window.oidc || !window.oidc.UserManager) {
    throw new Error('oidc-client-ts failed to load');
  }
  return new window.oidc.UserManager({
    authority: config.authority,
    client_id: config.clientId,
    redirect_uri: config.redirectUri,
    post_logout_redirect_uri: config.postLogoutRedirectUri,
    response_type: config.responseType,
    scope: config.scope,
    automaticSilentRenew: config.automaticSilentRenew,
    userStore: new window.oidc.WebStorageStateStore({ store: window.localStorage }),
  });
}

function getAuthToken() {
  return _currentAuthUser && (_currentAuthUser.id_token || _currentAuthUser.access_token);
}

function authFetch(input, init = {}) {
  const headers = new Headers(init.headers || {});
  const token = getAuthToken();
  if (token) headers.set('Authorization', `Bearer ${token}`);
  return fetch(input, { ...init, headers, credentials: init.credentials || 'same-origin' });
}

function isEmailAllowed(config, email) {
  const allowed = normalizeEmailList(config && config.allowedEmails);
  return allowed.length === 0 || allowed.includes(normalizeEmail(email));
}

function AuthProvider({ children }) {
  const [state, setState] = React.useState({
    enabled: true,
    configured: false,
    isLoading: true,
    isAuthenticated: false,
    activeNavigator: null,
    user: null,
    email: '',
    error: null,
    config: SPACETIME_AUTH_DEFAULTS,
  });
  const managerRef = React.useRef(null);

  React.useEffect(() => {
    let mounted = true;
    let cleanupEvents = () => {};

    const setUser = (user) => {
      _currentAuthUser = user || null;
      if (!mounted) return;
      setState(prev => ({
        ...prev,
        user: user || null,
        email: authUserEmail(user),
        isAuthenticated: Boolean(user && !user.expired),
        isLoading: false,
        error: null,
      }));
    };

    async function init() {
      try {
        const config = await loadSpacetimeAuthConfig();
        if (!config.enabled) {
          if (mounted) {
            setState(prev => ({
              ...prev,
              enabled: false,
              configured: true,
              isLoading: false,
              config,
            }));
          }
          return;
        }

        if (isPlaceholderClientId(config.clientId)) {
          if (mounted) {
            setState(prev => ({
              ...prev,
              enabled: true,
              configured: false,
              isLoading: false,
              config,
            }));
          }
          return;
        }

        const manager = createUserManager(config);
        managerRef.current = manager;

        const onUserLoaded = (user) => setUser(user);
        const onUserUnloaded = () => setUser(null);
        const onSilentRenewError = (error) => {
          if (mounted) setState(prev => ({ ...prev, error, isLoading: false }));
        };

        manager.events.addUserLoaded(onUserLoaded);
        manager.events.addUserUnloaded(onUserUnloaded);
        manager.events.addSilentRenewError(onSilentRenewError);
        cleanupEvents = () => {
          manager.events.removeUserLoaded(onUserLoaded);
          manager.events.removeUserUnloaded(onUserUnloaded);
          manager.events.removeSilentRenewError(onSilentRenewError);
        };

        if (mounted) {
          setState(prev => ({
            ...prev,
            enabled: true,
            configured: true,
            config,
            isLoading: true,
          }));
        }

        let user = null;
        if (hasOidcCallbackParams()) {
          try {
            user = await manager.signinCallback();
          } catch (signinError) {
            try {
              await manager.signoutCallback();
              await manager.removeUser();
              user = null;
            } catch {
              throw signinError;
            }
          }
          cleanAuthUrl(user);
        } else {
          user = await manager.getUser();
          if (user && user.expired) {
            await manager.removeUser();
            user = null;
          }
        }
        setUser(user);
      } catch (error) {
        if (mounted) {
          setState(prev => ({
            ...prev,
            error,
            isLoading: false,
            isAuthenticated: false,
          }));
        }
      }
    }

    init();
    return () => {
      mounted = false;
      cleanupEvents();
    };
  }, []);

  const signinRedirect = React.useCallback(async () => {
    const manager = managerRef.current;
    if (!manager) return;
    setState(prev => ({ ...prev, activeNavigator: 'signinRedirect', error: null }));
    try {
      await manager.signinRedirect({
        state: { returnHash: window.location.hash || '#/' },
      });
    } catch (error) {
      setState(prev => ({ ...prev, activeNavigator: null, error }));
    }
  }, []);

  const signoutRedirect = React.useCallback(async () => {
    const manager = managerRef.current;
    if (!manager) return;
    setState(prev => ({ ...prev, activeNavigator: 'signoutRedirect', error: null }));
    try {
      const idToken = _currentAuthUser && _currentAuthUser.id_token;
      await manager.removeUser();
      _currentAuthUser = null;
      setState(prev => ({
        ...prev,
        user: null,
        email: '',
        isAuthenticated: false,
      }));
      await manager.signoutRedirect({ id_token_hint: idToken });
    } catch (error) {
      await manager.removeUser();
      _currentAuthUser = null;
      setState(prev => ({
        ...prev,
        activeNavigator: null,
        user: null,
        email: '',
        isAuthenticated: false,
        error,
      }));
    }
  }, []);

  const removeUser = React.useCallback(async () => {
    const manager = managerRef.current;
    if (manager) await manager.removeUser();
    _currentAuthUser = null;
    setState(prev => ({
      ...prev,
      user: null,
      email: '',
      isAuthenticated: false,
    }));
  }, []);

  const value = React.useMemo(() => ({
    ...state,
    signinRedirect,
    signoutRedirect,
    removeUser,
  }), [state, signinRedirect, signoutRedirect, removeUser]);

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

function useAuth() {
  return React.useContext(AuthContext);
}

function AuthGate({ children }) {
  const auth = useAuth();
  if (!auth.enabled) return <>{children}</>;
  if (auth.isLoading) return <AuthPanel kind="loading" />;
  if (!auth.configured) return <AuthSetupPanel />;
  if (auth.error) return <AuthPanel kind="error" error={auth.error} onPrimary={auth.removeUser} />;
  if (!auth.isAuthenticated) return <AuthPanel kind="login" onPrimary={auth.signinRedirect} activeNavigator={auth.activeNavigator} />;

  if (!isEmailAllowed(auth.config, auth.email)) {
    return <AuthPanel kind="not-approved" email={auth.email} onPrimary={auth.signoutRedirect} />;
  }

  return <>{children}</>;
}

function AuthShellChrome() {
  const { lang, setLang } = useLang();
  const { theme, setTheme } = useTheme();
  return (
    <div className="auth-chrome">
      <button
        className="g-nav__theme"
        onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
        title={theme === 'dark' ? '切换亮色' : 'Switch to dark'}
      >
        {theme === 'dark' ? '☀' : '☾'}
      </button>
      <button
        className="g-nav__lang"
        onClick={() => setLang(lang === 'zh' ? 'en' : 'zh')}
      >
        {lang === 'zh' ? 'EN' : '中'}
      </button>
    </div>
  );
}

function AuthSetupPanel() {
  return (
    <main className="auth-shell">
      <AuthShellChrome />
      <section className="auth-panel">
        <p className="auth-kicker"><T zh="需要配置" en="Configuration required" /></p>
        <h1><T zh="SpacetimeAuth 尚未连接" en="SpacetimeAuth is not connected" /></h1>
        <p>
          <T
            zh="请设置 SpacetimeAuth client ID。可在 auth-config.js 中填写，或在 Cloudflare Pages 环境变量中设置 SPACETIME_AUTH_CLIENT_ID。"
            en="Set the SpacetimeAuth client ID in auth-config.js, or provide SPACETIME_AUTH_CLIENT_ID in Cloudflare Pages." />
        </p>
        <p className="auth-note">
          <T
            zh="Client ID 不是密钥；不要在前端或仓库中放入 client secret。"
            en="The client ID is not a secret; do not put a client secret in the frontend or repository." />
        </p>
      </section>
    </main>
  );
}

function AuthPanel({ kind, error, email, onPrimary, activeNavigator }) {
  const busy = Boolean(activeNavigator);
  const [showEmail, setShowEmail] = React.useState(false);
  React.useEffect(() => setShowEmail(false), [email]);

  const content = {
    loading: {
      kicker: <T zh="正在检查会话" en="Checking session" />,
      title: <T zh="加载中……" en="Loading..." />,
      body: <T zh="正在读取当前登录状态。" en="Reading the current sign-in state." />,
    },
    login: {
      kicker: <T zh="私密阅读" en="Private reading" />,
      title: <T zh="登录 Slow Compact" en="Log in to Slow Compact" />,
      body: <T zh="仅已预先批准的邮箱可进入。使用 SpacetimeAuth 邮件魔法链接登录。" en="Only pre-approved email addresses can enter. Use SpacetimeAuth email magic link login." />,
      button: busy ? <T zh="跳转中……" en="Redirecting..." /> : <T zh="使用邮箱登录" en="Log in with email" />,
    },
    error: {
      kicker: <T zh="登录失败" en="Sign-in failed" />,
      title: <T zh="无法完成登录" en="Could not finish sign-in" />,
      body: error && error.message ? error.message : <T zh="请重新登录。" en="Please try signing in again." />,
      button: <T zh="清除本地会话" en="Clear local session" />,
    },
    'not-approved': {
      kicker: <T zh="未获批准" en="Not approved" />,
      title: <T zh="此邮箱不能访问" en="This email cannot access the app" />,
      body: (
        <>
          {email ? <EmailText email={email} reveal={showEmail} className="auth-email" /> : <T zh="当前账号" en="This account" />}
          {' '}
          <T zh="不在本地允许名单中。" en="is not on the local allowlist." />
          {email && (
            <>
              {' '}
              <button
                type="button"
                className="auth-email-toggle"
                aria-pressed={showEmail}
                onClick={() => setShowEmail(v => !v)}
              >
                {showEmail ? <T zh="隐藏邮箱" en="Hide email" /> : <T zh="显示邮箱" en="Show email" />}
              </button>
            </>
          )}
        </>
      ),
      button: <T zh="退出登录" en="Sign out" />,
    },
  }[kind];

  return (
    <main className="auth-shell">
      <AuthShellChrome />
      <section className="auth-panel">
        <p className="auth-kicker">{content.kicker}</p>
        <h1>{content.title}</h1>
        <p>{content.body}</p>
        {content.button && (
          <button className="auth-primary" onClick={onPrimary} disabled={busy}>
            {content.button}
          </button>
        )}
      </section>
    </main>
  );
}

Object.assign(window, {
  AuthContext,
  AuthProvider,
  AuthGate,
  useAuth,
  authFetch,
  getAuthToken,
  authUserEmail,
  obfuscateEmail,
  EmailText,
  isEmailAllowed,
});
