import * as Clipboard from "expo-clipboard";
import { useCallback, useEffect, useRef, useState } from "react";
import { AppState, Text, TouchableOpacity, View } from "react-native";

import { InputField } from "@/components/input-field";
import { icons } from "@/constants";

// `\b` won't match between two digits, so a longer run like an order number
// never yields a false positive.
const CODE_PATTERN = /\b\d{6}\b/;

/** Pulls the 6-digit code out of whatever the user copied from the email. */
export const extractCode = (raw: string | null | undefined): string | null =>
  raw ? (CODE_PATTERN.exec(raw)?.[0] ?? null) : null;

type OtpFieldProps = {
  label?: string;
  value: string;
  onChange: (code: string) => void;
  /** Fired once the field holds a complete 6-digit code. */
  onComplete?: (code: string) => void;
};

/**
 * Code entry for the emailed verification/reset codes.
 *
 * Three ways in, cheapest first:
 *   1. iOS surfaces the code above the keyboard once Mail has it —
 *      `textContentType="oneTimeCode"` is what opts the field into that.
 *   2. Gmail's notification carries a "Copy code" action (Android) and the
 *      code is one long-press away on any platform: coming back to the app
 *      with a code on the clipboard raises the paste chip below.
 *   3. Typing it.
 */
export const OtpField = ({
  label = "Code",
  value,
  onChange,
  onComplete,
}: OtpFieldProps) => {
  const [pasteReady, setPasteReady] = useState(false);
  const completedFor = useRef<string | null>(null);

  // `hasStringAsync` inspects the clipboard without reading it, so it never
  // trips the iOS paste prompt — that only fires on the explicit tap below.
  const refreshPasteChip = useCallback(async () => {
    try {
      setPasteReady(await Clipboard.hasStringAsync());
    } catch {
      setPasteReady(false);
    }
  }, []);

  useEffect(() => {
    void refreshPasteChip();

    // The user leaves for Gmail and comes back with the code copied.
    const subscription = AppState.addEventListener("change", (state) => {
      if (state === "active") void refreshPasteChip();
    });

    return () => subscription.remove();
  }, [refreshPasteChip]);

  const handleChange = useCallback(
    (next: string) => {
      // Paste of a whole line ("123456 is your Waseel…") still lands the code.
      const digits =
        next.length > 6
          ? (extractCode(next) ?? next.replace(/\D/g, "").slice(0, 6))
          : next.replace(/\D/g, "");

      onChange(digits);
    },
    [onChange],
  );

  const onPastePress = useCallback(async () => {
    try {
      const code = extractCode(await Clipboard.getStringAsync());

      if (code) {
        onChange(code);
        return;
      }
    } catch {
      // Fall through to the hint below.
    }

    setPasteReady(false);
  }, [onChange]);

  // Auto-submit on a complete code, but only once per distinct code so a
  // rejected code isn't resubmitted on every re-render.
  useEffect(() => {
    if (value.length !== 6 || !onComplete) return;
    if (completedFor.current === value) return;

    completedFor.current = value;
    onComplete(value);
  }, [value, onComplete]);

  return (
    <View>
      <InputField
        label={label}
        icon={icons.lock}
        placeholder="123456"
        value={value}
        onChangeText={handleChange}
        keyboardType="number-pad"
        maxLength={6}
        // iOS reads codes out of Mail; Android's autofill only covers SMS, so
        // there the paste chip is the fast path.
        textContentType="oneTimeCode"
        autoComplete="one-time-code"
        importantForAutofill="yes"
        inputStyles="tracking-[8px] text-lg"
      />

      {pasteReady && value.length < 6 ? (
        <TouchableOpacity
          onPress={onPastePress}
          activeOpacity={0.7}
          className="self-start mt-2 rounded-full bg-primary-500/10 px-4 py-2"
        >
          <Text className="text-primary-500 font-JakartaSemiBold text-sm">
            Paste code from Gmail
          </Text>
        </TouchableOpacity>
      ) : null}
    </View>
  );
};
