"use client";

import Link from "next/link";
import { useEffect, useMemo, useRef, useState } from "react";
import { DismissibleBottomToast } from "@/components/dismissible-bottom-toast";
import {
  getWwwDomainInputMessage,
  hasWwwDomainPrefix,
} from "@/lib/domain-input";

type SignupStepId = "account" | "domain" | "mailbox" | "dkim" | "welcome";

type SignupProgressStatus = "idle" | "running" | "complete" | "error";

type SignupStepState = {
  description: string;
  id: SignupStepId;
  status: SignupProgressStatus;
  title: string;
};

type SignupProgressChunk =
  | {
      description: string;
      status: "running" | "complete";
      step: SignupStepId;
      title: string;
      type: "progress";
    }
  | {
      redirectUrl: string;
      type: "complete";
    }
  | {
      error: string;
      message: string;
      redirectUrl?: string;
      type: "error";
    };

type SignupRecoverySendResponse = {
  challengeId: number;
  error?: string;
  expiresAt?: string;
  message?: string;
  resendAvailableAt?: string;
  secondsRemaining?: number;
};

type SignupRecoveryVerifyResponse = {
  challengeId: number;
  error?: string;
  expiresAt?: string;
  message?: string;
  verificationToken?: string;
};

const DEFAULT_STEPS: SignupStepState[] = [
  {
    id: "account",
    title: "대표 계정 생성",
    description: "가입 정보와 기본 메일함 구조를 먼저 저장합니다.",
    status: "idle",
  },
  {
    id: "domain",
    title: "도메인 등록",
    description: "메일 서버에 도메인을 연결할 준비를 진행합니다.",
    status: "idle",
  },
  {
    id: "mailbox",
    title: "대표 메일 생성",
    description: "대표 메일 계정과 로그인 인증값을 준비합니다.",
    status: "idle",
  },
  {
    id: "dkim",
    title: "DNS 보안값 준비",
    description: "MX, SPF, DKIM, DMARC 연결값을 완성합니다.",
    status: "idle",
  },
  {
    id: "welcome",
    title: "초기 메일함 정리",
    description: "환영 메일과 첫 받은메일함 동기화를 마무리합니다.",
    status: "idle",
  },
];

function mapSignupErrorMessage(error: string, fallback: string) {
  if (error === "required") {
    return "사업자명, 도메인, 대표 메일 아이디, 비밀번호, 보조 이메일을 모두 입력해주세요.";
  }

  if (error === "password-mismatch") {
    return "비밀번호 확인이 일치하지 않습니다.";
  }

  if (error === "recovery-email-required") {
    return "보조 이메일을 입력해주세요.";
  }

  if (error === "recovery-email-invalid") {
    return "보조 이메일 형식이 올바르지 않습니다.";
  }

  if (error === "recovery-email-unverified") {
    return "보조 이메일 인증을 완료해야 회원가입할 수 있습니다.";
  }

  if (error === "recovery-email-expired") {
    return "보조 이메일 인증이 만료되었습니다. 인증번호를 다시 받아주세요.";
  }

  if (error === "email-exists") {
    return "이미 등록된 대표 메일 주소입니다. 로그인 또는 비밀번호 재설정을 이용해주세요.";
  }

  if (error === "domain-taken") {
    return "이미 다른 계정에서 사용 중인 도메인입니다.";
  }

  if (error === "domain-recovery-owner-mismatch") {
    return "이 도메인은 이전 가입에 사용한 대표 메일 주소로만 복구할 수 있습니다.";
  }

  if (error === "domain-recovery-verification-required") {
    return "이 도메인을 복구하려면 이전 가입에 사용한 보조 이메일 인증이 필요합니다.";
  }

  if (error === "domain-www-prefix") {
    return fallback || getWwwDomainInputMessage("");
  }

  if (error === "invalid-local-part") {
    return "대표 메일 아이디는 영문 소문자, 숫자, 점(.), 하이픈(-), 밑줄(_)만 사용할 수 있습니다.";
  }

  if (error === "mail-config") {
    return "메일 서버 설정값이 올바르지 않습니다.";
  }

  if (error === "mail-auth") {
    return "메일 서버 API 인증에 실패했습니다.";
  }

  if (error === "mail-domain-exists") {
    return "메일 서버에 이미 같은 도메인이 있어 자동 연결하지 못했습니다.";
  }

  if (error === "mail-setup") {
    return "메일 서버 생성 중 오류가 발생했습니다.";
  }

  return fallback || "회원가입 처리 중 오류가 발생했습니다.";
}

function normalizeEmail(value: string) {
  return value.trim().toLowerCase();
}

function formatCountdown(totalSeconds: number) {
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;

  return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
}

function StatusIcon({ status }: { status: SignupProgressStatus }) {
  if (status === "complete") {
    return (
      <span className="flex h-9 w-9 items-center justify-center rounded-full bg-[#2f6df6] text-white shadow-[0_12px_28px_rgba(47,109,246,0.24)]">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          strokeWidth="2.2"
          strokeLinecap="round"
          strokeLinejoin="round"
          className="h-[17px] w-[17px]"
        >
          <path d="m5 12 4.2 4.2L19 6.7" />
        </svg>
      </span>
    );
  }

  if (status === "running") {
    return (
      <span className="flex h-9 w-9 items-center justify-center rounded-full border border-[#cfe0ff] bg-[#f4f8ff] text-[#2f6df6]">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          strokeWidth="1.9"
          strokeLinecap="round"
          strokeLinejoin="round"
          className="h-[17px] w-[17px] animate-spin"
        >
          <path d="M21 12a9 9 0 1 1-2.64-6.36" />
          <path d="M21 3v6h-6" />
        </svg>
      </span>
    );
  }

  if (status === "error") {
    return (
      <span className="flex h-9 w-9 items-center justify-center rounded-full bg-[#fff1f3] text-[#df3866] shadow-[0_10px_24px_rgba(223,56,102,0.14)]">
        <svg
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
          className="h-[16px] w-[16px]"
        >
          <path d="m7 7 10 10" />
          <path d="m17 7-10 10" />
        </svg>
      </span>
    );
  }

  return (
    <span className="h-9 w-9 rounded-full border border-[#dbe4f3] bg-white" />
  );
}

function SignupProgressOverlay({
  activeMailbox,
  open,
  steps,
}: {
  activeMailbox: string;
  open: boolean;
  steps: SignupStepState[];
}) {
  if (!open) {
    return null;
  }

  const activeStep =
    steps.find((step) => step.status === "running") ??
    steps.find((step) => step.status === "error") ??
    steps.findLast((step) => step.status === "complete") ??
    steps[0];

  return (
    <div className="fixed inset-0 z-[160] flex items-center justify-center bg-[rgba(6,10,18,0.52)] px-4 py-6 backdrop-blur-[18px]">
      <div className="w-full max-w-[580px] overflow-hidden rounded-[30px] border border-white/35 bg-[linear-gradient(180deg,rgba(255,255,255,0.97),rgba(246,249,255,0.94))] shadow-[0_34px_90px_rgba(7,16,34,0.28)]">
        <div className="border-b border-[#e7eefb] px-7 pb-6 pt-7 sm:px-8">
          <span className="om-badge om-badge-sm om-badge-info">
            회원가입 진행 중
          </span>
          <h2 className="mt-4 text-[23px] font-semibold tracking-[-0.05em] text-[#16233c] sm:text-[26px]">
            페이지를 닫지 마세요
          </h2>
          <p className="mt-3 text-[13px] leading-6 text-[#6f7f98] sm:text-[14px]">
            {activeMailbox
              ? `${activeMailbox} 대표 메일을 준비하는 동안 단계별로 완료 상태를 표시합니다.`
              : "대표 메일과 도메인 연결을 차례대로 준비하는 중입니다."}
          </p>
        </div>

        <div className="px-7 py-6 sm:px-8">
          <div className="rounded-[24px] border border-[#e7eefb] bg-white/90 px-5 py-5">
            <p className="text-[13px] font-semibold text-[#2f6df6]">
              현재 단계
            </p>
            <p className="mt-2 text-[18px] font-semibold tracking-[-0.04em] text-[#16233c] sm:text-[20px]">
              {activeStep?.title}
            </p>
            <p className="mt-2 text-[13px] leading-6 text-[#71819a]">
              {activeStep?.description}
            </p>
          </div>

          <div className="mt-5 space-y-3">
            {steps.map((step) => (
              <div
                key={step.id}
                className={`flex items-start gap-4 rounded-[22px] border px-4 py-4 transition-colors ${
                  step.status === "running"
                    ? "border-[#d6e3ff] bg-[#f7faff]"
                    : step.status === "complete"
                      ? "border-[#e3ebfb] bg-white"
                      : step.status === "error"
                        ? "border-[#ffd9e2] bg-[#fff8fa]"
                        : "border-[#eef2f9] bg-[#fbfcff]"
                }`}
              >
                <StatusIcon status={step.status} />
                <div className="min-w-0">
                  <p className="text-[14px] font-semibold tracking-[-0.03em] text-[#16233c] sm:text-[15px]">
                    {step.title}
                  </p>
                  <p className="mt-1 text-[12px] leading-5 text-[#70819d] sm:text-[13px] sm:leading-6">
                    {step.description}
                  </p>
                </div>
              </div>
            ))}
          </div>

          <p className="mt-5 text-center text-[11px] leading-5 text-[#7f8da5] sm:text-[12px]">
            가입이 끝나면 자동으로 메일 워크스페이스로 이동합니다.
          </p>
        </div>
      </div>
    </div>
  );
}

async function readSignupStream(
  response: Response,
  onChunk: (chunk: SignupProgressChunk) => Promise<void> | void,
) {
  if (!response.body) {
    throw new Error("stream-missing");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();

    if (done) {
      break;
    }

    buffer += decoder.decode(value, { stream: true });
    let lineBreakIndex = buffer.indexOf("\n");

    while (lineBreakIndex >= 0) {
      const rawLine = buffer.slice(0, lineBreakIndex).trim();
      buffer = buffer.slice(lineBreakIndex + 1);

      if (rawLine) {
        await onChunk(JSON.parse(rawLine) as SignupProgressChunk);
      }

      lineBreakIndex = buffer.indexOf("\n");
    }
  }

  const tail = buffer.trim();

  if (tail) {
    await onChunk(JSON.parse(tail) as SignupProgressChunk);
  }
}

export function SignupPageForm({
  initialErrorMessage,
}: {
  initialErrorMessage: string | null;
}) {
  const abortControllerRef = useRef<AbortController | null>(null);
  const [notice, setNotice] = useState<{
    message: string;
    tone: "error" | "success";
  } | null>(
    initialErrorMessage
      ? { tone: "error", message: initialErrorMessage }
      : null,
  );
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [overlayOpen, setOverlayOpen] = useState(false);
  const [activeMailbox, setActiveMailbox] = useState("");
  const [steps, setSteps] = useState<SignupStepState[]>(DEFAULT_STEPS);
  const [recoveryEmail, setRecoveryEmail] = useState("");
  const [showRecoveryCodeInput, setShowRecoveryCodeInput] = useState(false);
  const [recoveryCode, setRecoveryCode] = useState("");
  const [recoveryChallengeId, setRecoveryChallengeId] = useState<number | null>(
    null,
  );
  const [recoveryVerificationToken, setRecoveryVerificationToken] =
    useState("");
  const [recoveryExpiresAtMs, setRecoveryExpiresAtMs] = useState<number | null>(
    null,
  );
  const [recoveryResendAvailableAtMs, setRecoveryResendAvailableAtMs] =
    useState<number | null>(null);
  const [isSendingRecoveryCode, setIsSendingRecoveryCode] = useState(false);
  const [isVerifyingRecoveryCode, setIsVerifyingRecoveryCode] = useState(false);
  const [nowMs, setNowMs] = useState(() => Date.now());

  useEffect(() => {
    return () => {
      abortControllerRef.current?.abort();
    };
  }, []);

  useEffect(() => {
    if (
      !showRecoveryCodeInput &&
      !recoveryExpiresAtMs &&
      !recoveryResendAvailableAtMs
    ) {
      return;
    }

    const timer = window.setInterval(() => {
      setNowMs(Date.now());
    }, 1000);

    return () => window.clearInterval(timer);
  }, [recoveryExpiresAtMs, recoveryResendAvailableAtMs, showRecoveryCodeInput]);

  const recoverySecondsRemaining = recoveryExpiresAtMs
    ? Math.max(0, Math.ceil((recoveryExpiresAtMs - nowMs) / 1000))
    : 0;
  const resendSecondsRemaining = recoveryResendAvailableAtMs
    ? Math.max(0, Math.ceil((recoveryResendAvailableAtMs - nowMs) / 1000))
    : 0;
  const isRecoveryVerified = Boolean(
    recoveryChallengeId &&
    recoveryVerificationToken &&
    recoverySecondsRemaining > 0,
  );
  const isRecoveryExpired =
    showRecoveryCodeInput &&
    recoveryExpiresAtMs !== null &&
    recoverySecondsRemaining <= 0;
  const canSubmit = useMemo(
    () => !isSubmitting && isRecoveryVerified,
    [isRecoveryVerified, isSubmitting],
  );

  const resetSteps = () => {
    setSteps(DEFAULT_STEPS);
  };

  const resetRecoveryVerification = () => {
    setShowRecoveryCodeInput(false);
    setRecoveryCode("");
    setRecoveryChallengeId(null);
    setRecoveryVerificationToken("");
    setRecoveryExpiresAtMs(null);
    setRecoveryResendAvailableAtMs(null);
  };

  const updateStep = (
    stepId: SignupStepId,
    status: SignupProgressStatus,
    title?: string,
    description?: string,
  ) => {
    setSteps((current) =>
      current.map((step) => {
        if (step.id !== stepId) {
          return status === "running" && step.status === "running"
            ? { ...step, status: "complete" }
            : step;
        }

        return {
          ...step,
          status,
          title: title ?? step.title,
          description: description ?? step.description,
        };
      }),
    );
  };

  const markUntil = (stepId: SignupStepId) => {
    setSteps((current) => {
      let shouldComplete = true;

      return current.map((step) => {
        if (step.id === stepId) {
          shouldComplete = false;
          return step;
        }

        if (shouldComplete && step.status !== "complete") {
          return { ...step, status: "complete" };
        }

        return step;
      });
    });
  };

  const handleRecoveryEmailChange = (
    event: React.ChangeEvent<HTMLInputElement>,
  ) => {
    const nextValue = event.target.value;

    if (normalizeEmail(nextValue) !== normalizeEmail(recoveryEmail)) {
      resetRecoveryVerification();
    }

    setRecoveryEmail(nextValue);
  };

  const handleSendRecoveryCode = async () => {
    const normalizedRecoveryEmail = normalizeEmail(recoveryEmail);

    if (!normalizedRecoveryEmail) {
      setNotice({
        tone: "error",
        message: "보조 이메일을 입력해주세요.",
      });
      return;
    }

    if (resendSecondsRemaining > 0) {
      setNotice({
        tone: "error",
        message: `${resendSecondsRemaining}초 뒤에 재발송 가능합니다.`,
      });
      return;
    }

    setShowRecoveryCodeInput(true);
    setIsSendingRecoveryCode(true);
    setRecoveryVerificationToken("");
    setNotice(null);

    try {
      const response = await fetch("/api/auth/signup-recovery/send", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          challengeId: recoveryChallengeId,
          recoveryEmail: normalizedRecoveryEmail,
        }),
      });
      const payload = (await response
        .json()
        .catch(() => null)) as SignupRecoverySendResponse | null;

      if (
        !response.ok ||
        !payload?.challengeId ||
        !payload.expiresAt ||
        !payload.resendAvailableAt
      ) {
        throw new Error(
          payload?.message ?? "인증번호 전송 중 오류가 발생했습니다.",
        );
      }

      setRecoveryChallengeId(payload.challengeId);
      setRecoveryVerificationToken("");
      setRecoveryCode("");
      setRecoveryExpiresAtMs(Date.parse(payload.expiresAt));
      setRecoveryResendAvailableAtMs(Date.parse(payload.resendAvailableAt));
      setNotice({
        tone: "success",
        message: recoveryChallengeId
          ? "인증번호를 다시 보냈습니다."
          : "인증번호를 보냈습니다. 3분 안에 입력해주세요.",
      });
    } catch (error) {
      setNotice({
        tone: "error",
        message:
          error instanceof Error
            ? error.message
            : "인증번호 전송 중 오류가 발생했습니다.",
      });
    } finally {
      setIsSendingRecoveryCode(false);
    }
  };

  const handleVerifyRecoveryCode = async () => {
    const normalizedRecoveryEmail = normalizeEmail(recoveryEmail);

    if (!recoveryChallengeId) {
      setNotice({
        tone: "error",
        message: "먼저 인증번호를 받아주세요.",
      });
      return;
    }

    if (isRecoveryExpired) {
      setNotice({
        tone: "error",
        message: "인증시간이 만료되었습니다. 인증번호를 다시 받아주세요.",
      });
      return;
    }

    if (!recoveryCode.trim()) {
      setNotice({
        tone: "error",
        message: "인증번호를 입력해주세요.",
      });
      return;
    }

    setIsVerifyingRecoveryCode(true);
    setNotice(null);

    try {
      const response = await fetch("/api/auth/signup-recovery/verify", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          challengeId: recoveryChallengeId,
          code: recoveryCode.trim(),
          recoveryEmail: normalizedRecoveryEmail,
        }),
      });
      const payload = (await response
        .json()
        .catch(() => null)) as SignupRecoveryVerifyResponse | null;

      if (!response.ok || !payload?.verificationToken || !payload?.expiresAt) {
        if (payload?.error === "expired") {
          setRecoveryExpiresAtMs(Date.now());
        }

        throw new Error(
          payload?.message ?? "인증번호 확인 중 오류가 발생했습니다.",
        );
      }

      setRecoveryVerificationToken(payload.verificationToken);
      setRecoveryChallengeId(payload.challengeId);
      setRecoveryExpiresAtMs(Date.parse(payload.expiresAt));
      setNotice({
        tone: "success",
        message: "보조 이메일 인증이 완료되었습니다.",
      });
    } catch (error) {
      setRecoveryVerificationToken("");
      setNotice({
        tone: "error",
        message:
          error instanceof Error
            ? error.message
            : "인증번호 확인 중 오류가 발생했습니다.",
      });
    } finally {
      setIsVerifyingRecoveryCode(false);
    }
  };

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    if (!canSubmit) {
      setNotice({
        tone: "error",
        message: isRecoveryExpired
          ? "보조 이메일 인증이 만료되었습니다. 인증번호를 다시 받아주세요."
          : "보조 이메일 인증을 완료해야 회원가입할 수 있습니다.",
      });
      return;
    }

    const formData = new FormData(event.currentTarget);
    const companyName = String(formData.get("companyName") ?? "").trim();
    const domain = String(formData.get("domain") ?? "")
      .trim()
      .toLowerCase();
    const localPart = String(formData.get("localPart") ?? "")
      .trim()
      .toLowerCase();
    const password = String(formData.get("password") ?? "");
    const passwordConfirm = String(formData.get("passwordConfirm") ?? "");
    const normalizedRecoveryEmail = normalizeEmail(recoveryEmail);
    const mailbox = domain && localPart ? `${localPart}@${domain}` : "";

    if (hasWwwDomainPrefix(domain)) {
      setNotice({
        tone: "error",
        message: getWwwDomainInputMessage(domain),
      });
      return;
    }

    if (!normalizedRecoveryEmail) {
      setNotice({
        tone: "error",
        message: "보조 이메일을 입력해주세요.",
      });
      return;
    }

    abortControllerRef.current?.abort();
    abortControllerRef.current = new AbortController();
    setNotice(null);
    resetSteps();
    updateStep(
      "account",
      "running",
      "대표 계정 생성 중",
      "가입 정보와 기본 메일함 구조를 먼저 저장하고 있습니다.",
    );
    setActiveMailbox(mailbox);
    setOverlayOpen(true);
    setIsSubmitting(true);

    try {
      const response = await fetch("/api/auth/signup", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          companyName,
          domain,
          localPart,
          password,
          passwordConfirm,
          recoveryEmail: normalizedRecoveryEmail,
          recoveryVerificationId: recoveryChallengeId,
          recoveryVerificationToken,
        }),
        credentials: "include",
        signal: abortControllerRef.current.signal,
      });

      if (!response.ok) {
        const payload = (await response.json().catch(() => null)) as {
          error?: string;
          message?: string;
        } | null;
        throw new Error(
          mapSignupErrorMessage(
            payload?.error ?? "system",
            payload?.message ?? "",
          ),
        );
      }

      await readSignupStream(response, async (chunk) => {
        if (chunk.type === "progress") {
          if (chunk.status === "running") {
            markUntil(chunk.step);
          }

          updateStep(chunk.step, chunk.status, chunk.title, chunk.description);
          return;
        }

        if (chunk.type === "error") {
          const message = mapSignupErrorMessage(chunk.error, chunk.message);
          setSteps((current) => {
            const activeStepId =
              [...current].findLast((step) => step.status === "running")?.id ??
              "welcome";

            return current.map((step) =>
              step.id === activeStepId
                ? {
                    ...step,
                    status: "error",
                    title: "가입 마무리 실패",
                    description: message,
                  }
                : step,
            );
          });

          if (chunk.redirectUrl) {
            const redirectUrl = chunk.redirectUrl;
            window.setTimeout(() => {
              window.location.assign(redirectUrl);
            }, 900);
            return;
          }

          throw new Error(message);
        }

        if (chunk.type === "complete") {
          setSteps((current) =>
            current.map((step) => ({
              ...step,
              status: "complete",
            })),
          );

          window.setTimeout(() => {
            window.location.assign(chunk.redirectUrl);
          }, 420);
        }
      });
    } catch (error) {
      if (error instanceof DOMException && error.name === "AbortError") {
        return;
      }

      setOverlayOpen(false);
      resetSteps();
      setNotice({
        tone: "error",
        message:
          error instanceof Error
            ? error.message
            : "회원가입 처리 중 오류가 발생했습니다.",
      });
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <>
      <DismissibleBottomToast
        notice={notice}
        clearSearchParams={["error", "success", "details"]}
      />

      <form onSubmit={handleSubmit} className="space-y-3.5">
        <label className="block">
          <span className="mb-1.5 block text-[13px] font-medium text-[#6f7784]">
            사업자명
          </span>
          <input
            required
            type="text"
            name="companyName"
            className="h-12 w-full rounded-[14px] border border-[#d7deea] bg-white px-4 text-[14px] text-[#101418] outline-none transition-colors focus:border-[#bcd1ff] focus:bg-white"
            placeholder="사업자명을 입력하세요"
          />
        </label>

        <div className="grid gap-4 sm:grid-cols-[minmax(0,1fr)_220px]">
          <label className="block">
            <span className="mb-1.5 block text-[13px] font-medium text-[#6f7784]">
              연결할 도메인
            </span>
            <input
              required
              type="text"
              name="domain"
              className="h-12 w-full rounded-[14px] border border-[#d7deea] bg-white px-4 text-[14px] text-[#101418] outline-none transition-colors focus:border-[#bcd1ff] focus:bg-white"
              placeholder="도메인을 입력하세요"
              onBlur={(event) => {
                if (!hasWwwDomainPrefix(event.currentTarget.value)) {
                  return;
                }

                setNotice({
                  tone: "error",
                  message: getWwwDomainInputMessage(event.currentTarget.value),
                });
              }}
            />
          </label>

          <label className="block">
            <span className="mb-1.5 block text-[13px] font-medium text-[#6f7784]">
              대표 메일 아이디
            </span>
            <input
              required
              type="text"
              name="localPart"
              pattern="^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$"
              className="h-12 w-full rounded-[14px] border border-[#d7deea] bg-white px-4 text-[14px] text-[#101418] outline-none transition-colors focus:border-[#bcd1ff] focus:bg-white"
              placeholder="대표 메일 아이디를 입력하세요"
            />
          </label>
        </div>

        <label className="block">
          <span className="mb-1.5 block text-[13px] font-medium text-[#6f7784]">
            대표 메일 비밀번호
          </span>
          <input
            required
            type="password"
            name="password"
            autoComplete="new-password"
            className="h-12 w-full rounded-[14px] border border-[#d7deea] bg-white px-4 text-[14px] text-[#101418] outline-none transition-colors focus:border-[#bcd1ff] focus:bg-white"
            placeholder="대표 메일 로그인에 사용할 비밀번호"
          />
        </label>

        <label className="block">
          <span className="mb-1.5 block text-[13px] font-medium text-[#6f7784]">
            비밀번호 확인
          </span>
          <input
            required
            type="password"
            name="passwordConfirm"
            autoComplete="new-password"
            className="h-12 w-full rounded-[14px] border border-[#d7deea] bg-white px-4 text-[14px] text-[#101418] outline-none transition-colors focus:border-[#bcd1ff] focus:bg-white"
            placeholder="비밀번호 다시 입력"
          />
        </label>

        <div className="pt-1">
          <div className="grid gap-3 sm:grid-cols-[minmax(0,1fr)_128px] sm:items-end">
            <label className="block">
              <span className="mb-1.5 block text-[13px] font-medium text-[#6f7784]">
                보조 이메일
              </span>
              <input
                required
                type="email"
                name="recoveryEmail"
                value={recoveryEmail}
                autoComplete="email"
                onChange={handleRecoveryEmailChange}
                className="h-12 w-full rounded-[14px] border border-[#d7deea] bg-white px-4 text-[14px] text-[#101418] outline-none transition-colors focus:border-[#bcd1ff] focus:bg-white"
                placeholder="비밀번호 찾기 링크를 받을 이메일"
              />
            </label>

            <button
              type="button"
              onClick={handleSendRecoveryCode}
              disabled={isSendingRecoveryCode}
              className="h-12 rounded-[14px] border border-[#d4def2] bg-white px-4 text-[13px] font-semibold text-[#24324a] transition-colors hover:border-[#b9cbed] hover:bg-[#f7faff] disabled:cursor-not-allowed disabled:opacity-60"
            >
              {isSendingRecoveryCode
                ? "전송 중..."
                : showRecoveryCodeInput
                  ? "재발송"
                  : "인증하기"}
            </button>
          </div>

          {showRecoveryCodeInput ? (
            <div className="mt-3">
              <div className="grid gap-3 sm:grid-cols-[minmax(0,1fr)_108px] sm:items-end">
                <label className="block">
                  <span className="mb-1.5 block text-[13px] font-medium text-[#6f7784]">
                    인증번호
                  </span>
                  <input
                    inputMode="numeric"
                    pattern="[0-9]*"
                    value={recoveryCode}
                    onChange={(event) =>
                      setRecoveryCode(
                        event.target.value.replace(/\D+/g, "").slice(0, 6),
                      )
                    }
                    className="h-12 w-full rounded-[14px] border border-[#d7deea] bg-white px-4 text-[14px] tracking-[0.12em] text-[#101418] outline-none transition-colors focus:border-[#bcd1ff] focus:bg-white"
                    placeholder="6자리 입력"
                  />
                </label>

                <button
                  type="button"
                  onClick={handleVerifyRecoveryCode}
                  disabled={isVerifyingRecoveryCode || isRecoveryVerified}
                  className="h-12 rounded-[14px] border border-[#2f6df6] bg-[#f4f8ff] px-4 text-[13px] font-semibold text-[#2f6df6] transition-colors hover:bg-[#edf4ff] disabled:cursor-not-allowed disabled:opacity-60"
                >
                  {isVerifyingRecoveryCode
                    ? "확인 중..."
                    : isRecoveryVerified
                      ? "인증완료"
                      : "확인"}
                </button>
              </div>

              <div className="mt-3 flex flex-wrap items-center justify-between gap-2 text-[12px]">
                <span
                  className={
                    isRecoveryExpired
                      ? "font-medium text-[#d9485c]"
                      : "text-[#64748b]"
                  }
                >
                  {isRecoveryExpired
                    ? "인증시간이 만료되었습니다. 인증번호를 다시 받아주세요."
                    : `남은 시간 ${formatCountdown(recoverySecondsRemaining)}`}
                </span>
                <span
                  className={
                    isRecoveryVerified
                      ? "font-semibold text-[#2f6df6]"
                      : resendSecondsRemaining > 0
                        ? "text-[#7f8791]"
                        : "text-[#7f8791]"
                  }
                >
                  {isRecoveryVerified
                    ? "보조 이메일 인증 완료"
                    : resendSecondsRemaining > 0
                      ? `${resendSecondsRemaining}초 뒤 재발송 가능`
                      : "재발송 가능"}
                </span>
              </div>
            </div>
          ) : (
            <p className="mt-2 text-[12px] leading-5 text-[#8a94a3]">
              보조 이메일은 비밀번호 찾기 링크를 받을 주소이며, 인증을 완료해야
              가입할 수 있습니다.
            </p>
          )}
        </div>

        <button
          type="submit"
          disabled={!canSubmit}
          className="om-btn-primary h-12 w-full rounded-[14px] px-5 text-[14px] font-semibold disabled:cursor-not-allowed"
        >
          {isSubmitting
            ? "가입 준비 중..."
            : isRecoveryVerified
              ? "무료로 시작하기"
              : "보조 이메일 인증 후 가입"}
        </button>
      </form>

      <div className="mt-5 text-center text-[13px] text-[#7f8791]">
        이미 계정이 있다면{" "}
        <Link href="/login" className="font-semibold text-[#2f6df6]">
          로그인
        </Link>
      </div>
      <p className="mt-4 text-center text-[12px] leading-5 text-[#9099a5]">
        가입이 완료되면 생성된 대표 메일 주소 자체가 관리자 로그인 계정이
        됩니다.
      </p>

      <SignupProgressOverlay
        open={overlayOpen}
        steps={steps}
        activeMailbox={activeMailbox}
      />
    </>
  );
}
