import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { Box, Button, Input, Spinner, Text, color } from 'folds';
import {
  Outlet,
  generatePath,
  matchPath,
  useLocation,
  useNavigate,
  useParams,
} from 'react-router-dom';
import { useTranslation } from 'react-i18next';

import { AuthFooter } from './AuthFooter';
import { AuthMascot } from './AuthMascot';
import { authLayoutRootVars } from './layoutConfig';
import * as css from './styles.css';
import {
  clientAllowedServer,
  clientDefaultServer,
  useClientConfig,
} from '../../hooks/useClientConfig';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { LOGIN_PATH, REGISTER_PATH, RESET_PASSWORD_PATH } from '../paths';
import { AutoDiscoveryAction, autoDiscovery } from '../../cs-api';
import { SpecVersionsLoader } from '../../components/SpecVersionsLoader';
import { SpecVersionsProvider } from '../../hooks/useSpecVersions';
import { AutoDiscoveryInfoProvider } from '../../hooks/useAutoDiscoveryInfo';
import { AuthFlowsLoader } from '../../components/AuthFlowsLoader';
import { AuthFlowsProvider } from '../../hooks/useAuthFlows';
import { AuthServerProvider } from '../../hooks/useAuthServer';
import { tryDecodeURIComponent } from '../../utils/dom';

const currentAuthPath = (pathname: string): string => {
  if (matchPath(LOGIN_PATH, pathname)) return LOGIN_PATH;
  if (matchPath(RESET_PASSWORD_PATH, pathname)) return RESET_PASSWORD_PATH;
  if (matchPath(REGISTER_PATH, pathname)) return REGISTER_PATH;
  return LOGIN_PATH;
};

function AuthLayoutLoading({ message }: { message: string }) {
  return (
    <Box justifyContent="Center" alignItems="Center" gap="200">
      <Spinner size="100" variant="Secondary" />
      <Text align="Center" size="T300">
        {message}
      </Text>
    </Box>
  );
}

function AuthLayoutError({ message }: { message: string }) {
  return (
    <Box justifyContent="Center" alignItems="Center" gap="200">
      <Text align="Center" style={{ color: color.Critical.Main }} size="T300">
        {message}
      </Text>
    </Box>
  );
}

/* ── Server edit dialog ── */
function ServerEditDialog({
  currentServer,
  onConfirm,
  onCancel,
}: {
  currentServer: string;
  onConfirm: (server: string) => void;
  onCancel: () => void;
}) {
  const { t } = useTranslation();
  const [value, setValue] = useState(currentServer);
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus();
    inputRef.current?.select();
  }, []);

  useEffect(() => {
    const handleKeyDown = (evt: KeyboardEvent) => {
      if (evt.key === 'Escape' && !evt.defaultPrevented && !evt.isComposing) {
        onCancel();
      }
    };

    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, [onCancel]);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const trimmed = value.trim();
    if (trimmed) onConfirm(trimmed);
  };

  return (
    <div className={css.ServerDialog} role="dialog" aria-modal="true" tabIndex={-1}>
      <div className={css.ServerDialogBackdrop} onClick={onCancel} aria-hidden="true" />
      <div className={css.ServerDialogCard}>
        <Text size="H3" style={{ color: '#e8e4df', fontWeight: 600 }}>
          {t('Auth.homeserver_dialog_title')}
        </Text>
        <Text size="T300" style={{ color: 'rgba(232, 228, 223, 0.65)' }}>
          {t('Auth.homeserver_dialog_desc')}
        </Text>
        <form onSubmit={handleSubmit}>
          <Box direction="Column" gap="400">
            <Input
              ref={inputRef}
              variant="Background"
              size="500"
              outlined
              value={value}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}
              placeholder={t('Auth.homeserver_dialog_placeholder')}
            />
            <Box gap="300" justifyContent="End">
              <Button type="button" variant="Secondary" fill="Soft" size="400" onClick={onCancel}>
                <Text size="B400">{t('Auth.homeserver_dialog_cancel')}</Text>
              </Button>
              <Button type="submit" variant="Primary" size="400">
                <Text size="B400">{t('Auth.homeserver_dialog_confirm')}</Text>
              </Button>
            </Box>
          </Box>
        </form>
      </div>
    </div>
  );
}

/* ── Layout helpers (from element-web) ── */

function readPx(s: CSSStyleDeclaration, name: string, fallback = 0): number {
  const v = Number.parseFloat(s.getPropertyValue(name));
  return Number.isFinite(v) ? v : fallback;
}

function readNum(s: CSSStyleDeclaration, name: string, fallback: number): number {
  const v = Number.parseFloat(s.getPropertyValue(name));
  return Number.isFinite(v) ? v : fallback;
}

function calculateModalLayout(input: {
  pageHeight: number;
  mascotTopOffset: number;
  mascotHeight: number;
  modalHeight: number;
  footerHeight: number;
  anchorRatio: number;
  minTop: number;
  bottomGap: number;
}) {
  const desiredTop = input.mascotTopOffset + input.mascotHeight * input.anchorRatio;
  const canFitAboveFooter =
    input.modalHeight <= input.pageHeight - input.minTop - input.footerHeight - input.bottomGap;
  const reservedFooterHeight = canFitAboveFooter ? input.footerHeight : 0;
  const reservedBottomGap = canFitAboveFooter ? input.bottomGap : 0;

  const maxTop = input.pageHeight - reservedFooterHeight - reservedBottomGap - input.modalHeight;
  const top = Math.max(input.minTop, Math.min(desiredTop, maxTop));
  const maxHeight = input.pageHeight - reservedFooterHeight - reservedBottomGap - top;

  return {
    top: Math.round(top),
    maxHeight: Math.max(0, Math.floor(maxHeight)),
    constrained: input.modalHeight > maxHeight + 1,
  };
}

export function AuthLayout() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const location = useLocation();
  const { server: urlEncodedServer } = useParams();

  const clientConfig = useClientConfig();
  const defaultServer = clientDefaultServer(clientConfig);
  let server: string = urlEncodedServer ? tryDecodeURIComponent(urlEncodedServer) : defaultServer;

  if (!clientAllowedServer(clientConfig, server)) {
    server = defaultServer;
  }

  const [showServerDialog, setShowServerDialog] = useState(false);

  const [discoveryState, discoverServer] = useAsyncCallback(
    useCallback(async (serverName: string) => {
      const response = await autoDiscovery(fetch, serverName);
      return { serverName, response };
    }, [])
  );

  useEffect(() => {
    if (server) discoverServer(server);
  }, [discoverServer, server]);

  useEffect(() => {
    if (!urlEncodedServer || tryDecodeURIComponent(urlEncodedServer) !== server) {
      navigate(
        generatePath(currentAuthPath(location.pathname), {
          server: encodeURIComponent(server),
        }),
        { replace: true }
      );
    }
  }, [urlEncodedServer, navigate, location, server]);

  const selectServer = useCallback(
    (newServer: string) => {
      if (newServer === server) {
        if (discoveryState.status === AsyncStatus.Loading) return;
        discoverServer(server);
        return;
      }
      navigate(
        generatePath(currentAuthPath(location.pathname), {
          server: encodeURIComponent(newServer),
        })
      );
    },
    [navigate, location, discoveryState, server, discoverServer]
  );

  const [autoDiscoveryError, autoDiscoveryInfo] =
    discoveryState.status === AsyncStatus.Success ? discoveryState.data.response : [];

  /* ── Page title based on route ── */
  const getPageTitle = () => {
    if (matchPath(LOGIN_PATH, location.pathname)) return t('Auth.title_login');
    if (matchPath(REGISTER_PATH, location.pathname)) return t('Auth.title_register');
    return t('Auth.title_reset_password');
  };
  const pageTitle = getPageTitle();

  /* ── Refs for JS-driven layout ── */
  const pageRef = useRef<HTMLDivElement>(null);
  const mascotRef = useRef<HTMLDivElement>(null);
  const cardContentRef = useRef<HTMLDivElement>(null);
  const cardBodyRef = useRef<HTMLDivElement>(null);
  const footerRef = useRef<HTMLElement>(null);

  useLayoutEffect(() => {
    const page = pageRef.current;
    const mascot = mascotRef.current;
    const cardContent = cardContentRef.current;
    const cardBody = cardBodyRef.current;
    const footer = footerRef.current;
    if (!page || !mascot || !cardContent || !cardBody || !footer) return undefined;

    const rootEl = document.getElementById('root');
    let frameId = 0;
    let padTop = 0;
    let padBottom = 0;

    // env(safe-area-inset-*) only changes on rotate / fullscreen toggle; both
    // fire window.resize. Recompute lazily, not on every RAF.
    const recomputePadding = (): void => {
      if (!rootEl) return;
      const s = getComputedStyle(rootEl);
      padTop = parseFloat(s.paddingTop) || 0;
      padBottom = parseFloat(s.paddingBottom) || 0;
    };
    recomputePadding();

    const update = (): void => {
      cancelAnimationFrame(frameId);
      frameId = requestAnimationFrame(() => {
        // Capacitor WebView can publish env(safe-area-inset-*) one frame after
        // mount; cheaper to refresh per RAF than to miss the first cold paint.
        recomputePadding();
        const s = getComputedStyle(page);
        const footerHeight =
          footer.offsetHeight || readPx(s, '--vojo-footer-space', footer.offsetHeight);
        const bottomGap = readPx(s, '--vojo-modal-gap');
        // AuthLayout is height: 100dvh inside #root which has env(safe-area-inset-*)
        // padding. AuthLayout overflows #root's content area by the safe-area
        // amounts and gets clipped by body { overflow: hidden }. Subtract #root's
        // vertical padding from pageHeight so the form fits the visible band.
        const rawPageHeight = window.visualViewport?.height ?? page.clientHeight;
        const pageHeight = Math.max(0, rawPageHeight - padTop - padBottom);

        const anchorRatio = readNum(s, '--vojo-anchor-ratio', 0.58);

        // Compact mode: form + footer don't fit in the viewport at all.
        // When they do fit, the form naturally docks at the mascot anchor and
        // the mascot peeks above through the glassmorphism — that's the design,
        // not a bug. We only drop the mascot when the math has nowhere to put
        // both. Triggers on keyboard up, very small windows, landscape, etc.
        const compact = cardBody.offsetHeight + footerHeight + bottomGap > pageHeight;

        const mascotTopOffset = compact ? 0 : readPx(s, '--vojo-mascot-top');
        const mascotHeight = compact ? 0 : mascot.offsetHeight;
        const minTop = compact ? 0 : readPx(s, '--vojo-modal-min-top');

        const layout = calculateModalLayout({
          pageHeight,
          mascotTopOffset,
          mascotHeight,
          modalHeight: cardBody.offsetHeight,
          footerHeight,
          anchorRatio,
          minTop,
          bottomGap,
        });
        page.style.setProperty('--vojo-modal-top', `${layout.top}px`);
        page.style.setProperty('--vojo-modal-max-h', `${layout.maxHeight}px`);
        cardContent.classList.toggle(css.AuthCardContentScrollable, layout.constrained);
        mascot.classList.toggle(css.AuthMascotHidden, compact);
      });
    };

    update();
    const ro = new ResizeObserver(update);
    ro.observe(page);
    ro.observe(mascot);
    ro.observe(cardBody);
    ro.observe(footer);
    const onWindowResize = (): void => {
      recomputePadding();
      update();
    };
    window.addEventListener('resize', onWindowResize);
    window.visualViewport?.addEventListener('resize', update);

    return () => {
      cancelAnimationFrame(frameId);
      ro.disconnect();
      window.removeEventListener('resize', onWindowResize);
      window.visualViewport?.removeEventListener('resize', update);
    };
  }, []);

  return (
    <div className={css.AuthLayout} style={authLayoutRootVars} ref={pageRef}>
      <div className={css.AuthStack}>
        <AuthMascot mascotRef={mascotRef} />

        <div className={css.AuthModalZone}>
          <div className={css.AuthCard}>
            <div className={css.AuthCardContent} ref={cardContentRef}>
              <div className={css.AuthCardBody} ref={cardBodyRef}>
                {/* Title */}
                <Text size="H2" style={{ color: '#e8e4df', fontWeight: 700 }}>
                  {pageTitle}
                </Text>

                {/* Server info row with separator */}
                <div className={css.ServerRow}>
                  <span className={css.ServerLabel}>{t('Auth.homeserver')}</span>
                  <span className={css.ServerName}>{server}</span>
                  {clientConfig.allowCustomHomeservers && (
                    <button
                      type="button"
                      className={css.ServerEdit}
                      onClick={() => setShowServerDialog(true)}
                    >
                      {t('Auth.homeserver_edit')}
                    </button>
                  )}
                </div>

                {/* Loading / error / form */}
                {discoveryState.status === AsyncStatus.Loading && (
                  <AuthLayoutLoading message={t('Auth.loading_server')} />
                )}
                {discoveryState.status === AsyncStatus.Error && (
                  <AuthLayoutError message={t('Auth.error_server_not_found')} />
                )}
                {autoDiscoveryError?.action === AutoDiscoveryAction.FAIL_PROMPT && (
                  <AuthLayoutError
                    message={t('Auth.error_server_config_invalid', {
                      host: autoDiscoveryError.host,
                    })}
                  />
                )}
                {autoDiscoveryError?.action === AutoDiscoveryAction.FAIL_ERROR && (
                  <AuthLayoutError message={t('Auth.error_server_base_url_invalid')} />
                )}
                {discoveryState.status === AsyncStatus.Success && autoDiscoveryInfo && (
                  <AuthServerProvider value={discoveryState.data.serverName}>
                    <AutoDiscoveryInfoProvider value={autoDiscoveryInfo}>
                      <SpecVersionsLoader
                        baseUrl={autoDiscoveryInfo['m.homeserver'].base_url}
                        fallback={() => (
                          <AuthLayoutLoading
                            message={t('Auth.loading_connecting', {
                              url: autoDiscoveryInfo['m.homeserver'].base_url,
                            })}
                          />
                        )}
                        error={() => (
                          <AuthLayoutError message={t('Auth.error_server_unavailable')} />
                        )}
                      >
                        {(specVersions) => (
                          <SpecVersionsProvider value={specVersions}>
                            <AuthFlowsLoader
                              fallback={() => (
                                <AuthLayoutLoading message={t('Auth.loading_auth_flows')} />
                              )}
                              error={() => <AuthLayoutError message={t('Auth.error_auth_flows')} />}
                            >
                              {(authFlows) => (
                                <AuthFlowsProvider value={authFlows}>
                                  <Outlet />
                                </AuthFlowsProvider>
                              )}
                            </AuthFlowsLoader>
                          </SpecVersionsProvider>
                        )}
                      </SpecVersionsLoader>
                    </AutoDiscoveryInfoProvider>
                  </AuthServerProvider>
                )}
              </div>
            </div>
          </div>
        </div>
      </div>
      <AuthFooter footerRef={footerRef} />

      {/* Server edit dialog */}
      {showServerDialog && (
        <ServerEditDialog
          currentServer={server}
          onConfirm={(newServer) => {
            setShowServerDialog(false);
            selectServer(newServer);
          }}
          onCancel={() => setShowServerDialog(false)}
        />
      )}
    </div>
  );
}
