'use client';
import { FormEvent, FunctionComponent, useEffect, useState } from 'react';
import { Box, CircularProgress, Link as MuiLink, Stack, Typography } from '@mui/material';
import { useLocale, useTranslations } from 'next-intl';

import OtpInput from '@/components/OtpInput';
import { maskIranMobile } from '@/components/PhoneNumberField';
import { AppButton } from '@/components';
import { APP_ROLES, type AppRole } from '@/constants';
import { useRequestOtp, useVerifyOtp } from '@/services/auth';
import { OTP_CODE_LENGTH, OTP_LOCKED_CODE } from '@/services/auth/constants';
import { formatClock } from '@/utils';
import type { ApiError } from '@/lib/api/errors';
import { useCountdown } from './useCountdown';
import { useWebOtp } from './useWebOtp';

interface OtpStepProps {
  phone: string;
  intendedRole: AppRole;
  /** Server's resend cooldown from the request step; drives the initial countdown. */
  resendSeconds: number;
  onVerified: () => void;
  onChangeNumber: () => void;
}

/**
 * A2 (customer) / B2 (nurse) — the OTP code step. Auto-verifies once the last box is filled
 * (manually or via WebOTP autofill), runs a single resend countdown (cleaned up on unmount), and
 * renders explicit wrong-code, expired-code and max-attempts-lockout states. Same mechanism for
 * both actors; the CTA copy differs.
 * @component OtpStep
 */
const OtpStep: FunctionComponent<OtpStepProps> = ({
  phone,
  intendedRole,
  resendSeconds,
  onVerified,
  onChangeNumber,
}) => {
  const t = useTranslations('auth');
  const locale = useLocale();
  const [code, setCode] = useState('');
  const [locked, setLocked] = useState(false);
  const countdown = useCountdown();
  const verifyOtp = useVerifyOtp();
  const resendOtp = useRequestOtp();

  const { start } = countdown;
  useEffect(() => start(resendSeconds), [start, resendSeconds]);

  const isNurse = intendedRole === APP_ROLES.NURSE;
  const hasError = verifyOtp.isError;

  const verify = (value: string) => {
    if (value.length !== OTP_CODE_LENGTH || verifyOtp.isPending || locked) return;
    verifyOtp.mutate(
      { phone, code: value },
      {
        onSuccess: onVerified,
        onError: (error) => {
          if ((error as ApiError).code === OTP_LOCKED_CODE) setLocked(true);
          setCode(''); // clear the boxes so the user can retry a fresh code
        },
      },
    );
  };

  // Reads the SMS code on a supporting browser (REQ-039) and feeds it through the same path
  // manual entry uses. Stops (aborts the pending read) once a verification is underway.
  useWebOtp((otpCode) => {
    setCode(otpCode);
    verify(otpCode);
  }, !locked && !verifyOtp.isPending);

  const submit = (event: FormEvent) => {
    event.preventDefault();
    verify(code);
  };

  const resend = () => {
    resendOtp.mutate(
      { phone },
      {
        onSuccess: (result) => {
          setLocked(false);
          setCode('');
          verifyOtp.reset();
          start(result.resendAvailableInSeconds);
        },
      },
    );
  };

  // While locked, requesting a new code is the way out — allow resend regardless of the timer.
  const resendDisabled = resendOtp.isPending || (countdown.isActive && !locked);

  return (
    <Stack component="form" onSubmit={submit} sx={{ gap: 2 }}>
      <Stack sx={{ gap: 0.5, textAlign: 'center' }}>
        <Typography variant="h6" component="h1">
          {t('otp_title')}
        </Typography>
        <Typography variant="body2" sx={{ color: 'text.secondary' }}>
          {t.rich('otp_sent_to', {
            // <bdi dir="ltr"> isolates the masked number so the RTL sentence's bidi algorithm
            // never reorders the digit/bullet runs around it (the classic digits-around-neutrals bug).
            phone: () => (
              <Box component="bdi" dir="ltr">
                {maskIranMobile(phone)}
              </Box>
            ),
          })}
        </Typography>
      </Stack>

      <OtpInput
        length={OTP_CODE_LENGTH}
        value={code}
        onChange={setCode}
        onComplete={verify}
        disabled={locked || verifyOtp.isPending}
        error={hasError}
        autoFocus
        aria-label={t('otp_title')}
      />

      {locked ? (
        <Typography variant="body2" sx={{ color: 'var(--bal-error)', textAlign: 'center' }}>
          {t('otp_locked')}
        </Typography>
      ) : hasError ? (
        <Typography variant="body2" sx={{ color: 'var(--bal-error)', textAlign: 'center' }}>
          {t('otp_invalid')}
        </Typography>
      ) : null}

      <AppButton
        type="submit"
        color="primary"
        variant="contained"
        fullWidth
        disabled={code.length !== OTP_CODE_LENGTH || verifyOtp.isPending || locked}
        startIcon={verifyOtp.isPending ? <CircularProgress size={18} color="inherit" /> : undefined}
      >
        {isNurse ? t('otp_verify_nurse') : t('otp_verify_customer')}
      </AppButton>

      <Stack sx={{ alignItems: 'center', gap: 1 }}>
        {resendDisabled && countdown.isActive && !locked ? (
          <Typography variant="body2" sx={{ color: 'text.secondary' }}>
            {t.rich('resend_in', {
              // Same bidi-isolation reasoning as the phone echo above — keeps mm:ss reading
              // left-to-right (in Persian digits on /fa) inside the RTL sentence.
              time: () => (
                <Box component="bdi" dir="ltr">
                  {formatClock(countdown.seconds, locale)}
                </Box>
              ),
            })}
          </Typography>
        ) : (
          <MuiLink
            component="button"
            type="button"
            onClick={resend}
            disabled={resendDisabled}
            underline="hover"
            sx={{ color: 'primary.main' }}
          >
            {t('resend')}
          </MuiLink>
        )}
        <MuiLink
          component="button"
          type="button"
          onClick={onChangeNumber}
          underline="hover"
          sx={{ color: 'text.secondary' }}
        >
          {t('change_number')}
        </MuiLink>
      </Stack>
    </Stack>
  );
};

export default OtpStep;
