import { debounce } from "lodash";
import { useCallback, useEffect, useMemo, useState } from "react";
import { FieldError, Input, Label } from "react-aria-components";
import { fetchQuery, graphql, useRelayEnvironment } from "react-relay";

import type { RegexFieldQuery } from "@phoenix/components/__generated__/RegexFieldQuery.graphql";
import { Text } from "@phoenix/components/core/content";
import type { TextFieldProps } from "@phoenix/components/core/field";
import {
  FieldDangerIcon,
  FieldSuccessIcon,
  TextField,
} from "@phoenix/components/core/field";

type RegexFieldProps = {
  value: string;
  onChange: (value: string) => void;
  isInvalid?: boolean;
  /**
   * Error message to display. If provided, this is shown instead of regex syntax errors.
   */
  error?: string;
  description?: string;
  label?: string;
  ariaLabel?: string;
  placeholder?: string;
  validateRegex?: boolean;
  /**
   * Callback fired when the internal regex validation state changes.
   * Called with `true` when the regex is valid, `false` when invalid.
   */
  onValidationChange?: (isValid: boolean) => void;
} & Omit<TextFieldProps, "isInvalid" | "onChange">;

const regexFieldQuery = graphql`
  query RegexFieldQuery($input: String!) {
    validateRegularExpression(regex: $input) {
      isValid
      errorMessage
    }
  }
`;

export const RegexField = ({
  value,
  onChange,
  error: externalError,
  isInvalid: externalIsInvalid,
  description,
  label,
  ariaLabel,
  placeholder,
  validateRegex = true,
  onValidationChange,
  ...textFieldProps
}: RegexFieldProps) => {
  const environment = useRelayEnvironment();
  const [internalError, setInternalError] = useState<string | undefined>(
    undefined
  );
  const [isValid, setIsValid] = useState(false);
  const [debouncedValue, setDebouncedValue] = useState(value);

  const debouncedSetValue = useMemo(() => {
    return debounce((newValue: string) => {
      setDebouncedValue(newValue);
    }, 250);
  }, []);

  useEffect(() => {
    return () => {
      debouncedSetValue.cancel();
    };
  }, [debouncedSetValue]);

  const handleChange = useCallback(
    (newValue: string) => {
      onChange(newValue);
      if (!validateRegex) {
        return;
      }
      if (!newValue) {
        debouncedSetValue.cancel();
        setDebouncedValue("");
        setInternalError(undefined);
        setIsValid(false);
        onValidationChange?.(false);
        return;
      }
      // Clear checkmark immediately on typing, but keep error
      // until revalidated to prevent helptext value flickering
      setIsValid(false);
      debouncedSetValue(newValue);
    },
    [onChange, debouncedSetValue, onValidationChange, validateRegex]
  );

  useEffect(() => {
    if (!validateRegex) {
      return undefined;
    }
    if (!debouncedValue) {
      return undefined;
    }

    const query = fetchQuery<RegexFieldQuery>(environment, regexFieldQuery, {
      input: debouncedValue,
    });

    const subscription = query.subscribe({
      next: (data) => {
        if (data.validateRegularExpression.isValid) {
          setInternalError(undefined);
          setIsValid(true);
          onValidationChange?.(true);
        } else {
          setInternalError(
            data.validateRegularExpression.errorMessage ??
              "Regular expression is invalid"
          );
          setIsValid(false);
          onValidationChange?.(false);
        }
      },
    });

    return () => {
      subscription.unsubscribe();
    };
  }, [validateRegex, debouncedValue, environment, onValidationChange]);

  const error = externalError || internalError;
  const hasError = externalIsInvalid || !!externalError || !!internalError;

  const renderValidationIcon = () => {
    if (!value) {
      return null;
    }

    if (externalIsInvalid || externalError || internalError) {
      return <FieldDangerIcon />;
    }

    if (isValid) {
      return <FieldSuccessIcon />;
    }

    return null;
  };

  return (
    <TextField
      isInvalid={hasError}
      aria-label={ariaLabel || label}
      value={value}
      onChange={handleChange}
      {...textFieldProps}
    >
      {label && <Label>{label}</Label>}
      <Input placeholder={placeholder} />
      {!error && description && <Text slot="description">{description}</Text>}
      {error && <FieldError>{error}</FieldError>}
      {renderValidationIcon()}
    </TextField>
  );
};
