/*
 * Copyright (c) 2021 - 2024, Ludvig Lundgren and the autobrr contributors.
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

import { useEffect } from "react";
import { Field, FieldProps, useFormikContext } from "formik";
import { EyeIcon, EyeSlashIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
import TextareaAutosize from "react-textarea-autosize";

import { useToggle } from "@hooks/hooks";
import { DocsTooltip } from "@components/tooltips/DocsTooltip";
import { classNames } from "@utils";

type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;

interface TextFieldProps {
  name: string;
  defaultValue?: string;
  label?: string;
  required?: boolean;
  placeholder?: string;
  columns?: COL_WIDTHS;
  autoComplete?: string;
  hidden?: boolean;
  disabled?: boolean;
  tooltip?: JSX.Element;
}

export const TextField = ({
  name,
  defaultValue,
  label,
  required,
  placeholder,
  columns,
  autoComplete,
  hidden,
  tooltip,
  disabled
}: TextFieldProps) => (
  <div
    className={classNames(
      "col-span-12",
      hidden ? "hidden" : "",
      columns ? `sm:col-span-${columns}` : ""
    )}
  >
    {label && (
      <label htmlFor={name} className="flex ml-px text-xs font-bold text-gray-800 dark:text-gray-100 uppercase tracking-wide">
        {tooltip ? (
          <DocsTooltip label={label}>{tooltip}</DocsTooltip>
        ) : label}
        {required ? (
          <span className="ml-1 text-red-500">*</span>
        ) : null}
      </label>
    )}
    <Field name={name} defaultValue={defaultValue}>
      {({
        field,
        meta
      }: FieldProps) => (
        <>
          <input
            {...field}
            name={name}
            type="text"
            defaultValue={defaultValue}
            autoComplete={autoComplete}
            className={classNames(
              meta.touched && meta.error
                ? "border-red-500 focus:ring-red-500 focus:border-red-500"
                : "border-gray-300 dark:border-gray-700 focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500",
              disabled
                ? "bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed"
                : "bg-gray-100 dark:bg-gray-815 dark:text-gray-100",
              "mt-1 block border w-full dark:text-gray-100 rounded-md"
            )}
            disabled={disabled}
            placeholder={placeholder}
            data-1p-ignore
          />

          {meta.touched && meta.error && (
            <p className="error text-sm text-red-600 mt-1">* {meta.error}</p>
          )}
        </>
      )}
    </Field>
  </div>
);

interface RegexFieldProps {
  name: string;
  defaultValue?: string;
  label?: string;
  placeholder?: string;
  columns?: COL_WIDTHS;
  autoComplete?: string;
  useRegex?: boolean;
  hidden?: boolean;
  disabled?: boolean;
  tooltip?: JSX.Element;
}

export const RegexField = ({
  name,
  defaultValue,
  label,
  placeholder,
  columns,
  autoComplete,
  useRegex,
  hidden,
  tooltip,
  disabled
}: RegexFieldProps) => {
  const validRegex = (pattern: string) => {

    // Check for unsupported lookahead and lookbehind assertions
    if (/\(\?<=|\(\?<!|\(\?=|\(\?!/.test(pattern)) {
      return false;
    }

    // Check for unsupported atomic groups
    if (/\(\?>/.test(pattern)) {
      return false;
    }

    // Check for unsupported recursive patterns
    if (/\(\?(R|0)\)/.test(pattern)) {
      return false;
    }

    // Check for unsupported possessive quantifiers
    if (/[*+?]{1}\+|\{[0-9]+,[0-9]*\}\+/.test(pattern)) {
      return false;
    }

    // Check for unsupported control verbs
    if (/\\g</.test(pattern)) {
      return false;
    }

    // Check for unsupported conditionals
    if (/\(\?\((\?[=!][^)]*)\)[^)]*\|?[^)]*\)/.test(pattern)) {
      return false;
    }

    // Check for unsupported backreferences
    if (/\\k</.test(pattern)) {
      return false;
    }

    // Check if the pattern is a valid regex
    try {
      new RegExp(pattern);
      return true;
    } catch (e) {
      return false;
    }
  };


  const validateRegexp = (val: string) => {
    let error = "";

    if (!validRegex(val)) {
      error = "Invalid regex";
    }

    return error;
  };

  const { validateForm } = useFormikContext();
  useEffect(() => {
    if (useRegex) {
      validateForm();
    }
  }, [useRegex, validateForm]);

  return (
    <div
      className={classNames(
        "col-span-12",
        hidden ? "hidden" : "",
        columns ? `sm:col-span-${columns}` : ""
      )}
    >
      {label && (
        <label
          htmlFor={name}
          className="flex ml-px text-xs font-bold text-gray-800 dark:text-gray-100 uppercase tracking-wide"
        >
          {tooltip ? (
            <DocsTooltip label={label}>{tooltip}</DocsTooltip>
          ) : label}
        </label>
      )}
      <Field
        name={name}
        validate={useRegex && validateRegexp}
      >
        {({ field, meta }: FieldProps) => (
          <div className="relative">
            <input
              {...field}
              name={name}
              type="text"
              defaultValue={defaultValue}
              autoComplete={autoComplete}
              className={classNames(
                useRegex && meta.error
                  ? "border-red-500 focus:ring-red-500 focus:border-red-500"
                  : "border-gray-300 dark:border-gray-700 focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500",
                disabled
                  ? "bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed"
                  : "bg-gray-100 dark:bg-gray-815 dark:text-gray-100",
                useRegex
                  ? "pr-10"
                  : "",
                "mt-1 block w-full dark:text-gray-100 rounded-md"
              )}
              disabled={disabled}
              placeholder={placeholder}
            />
            {useRegex && (
              <div className="relative">
                <div className="flex float-right items-center">
                  {!meta.error ? (
                    <CheckCircleIcon className="h-8 w-8 mb-2.5 pl-1 text-green-500 right-2 absolute transform -translate-y-1/2" aria-hidden="true" style={{ overflow: "hidden" }} />
                  ) : (
                    <XCircleIcon className="h-8 w-8 mb-2.5 pl-1 text-red-500 right-2 absolute transform -translate-y-1/2" aria-hidden="true" style={{ overflow: "hidden" }} />
                  )}
                </div>
              </div>
            )}
          </div>
        )}
      </Field>

    </div>
  );
};

export const RegexTextAreaField = ({
  name,
  defaultValue,
  label,
  placeholder,
  columns,
  autoComplete = "off",
  useRegex,
  hidden,
  tooltip,
  disabled
}: RegexFieldProps) => {
  const validRegex = (pattern: string) => {

    // Check for unsupported lookahead and lookbehind assertions
    if (/\(\?<=|\(\?<!|\(\?=|\(\?!/.test(pattern)) {
      return false;
    }

    // Check for unsupported atomic groups
    if (/\(\?>/.test(pattern)) {
      return false;
    }

    // Check for unsupported recursive patterns
    if (/\(\?(R|0)\)/.test(pattern)) {
      return false;
    }

    // Check for unsupported possessive quantifiers
    if (/[*+?]{1}\+|\{[0-9]+,[0-9]*\}\+/.test(pattern)) {
      return false;
    }

    // Check for unsupported control verbs
    if (/\\g</.test(pattern)) {
      return false;
    }

    // Check for unsupported conditionals
    if (/\(\?\((\?[=!][^)]*)\)[^)]*\|?[^)]*\)/.test(pattern)) {
      return false;
    }

    // Check for unsupported backreferences
    if (/\\k</.test(pattern)) {
      return false;
    }

    // Check if the pattern is a valid regex
    try {
      new RegExp(pattern);
      return true;
    } catch (e) {
      return false;
    }
  };


  const validateRegexp = (val: string) => {
    let error = "";

    if (!validRegex(val)) {
      error = "Invalid regex";
    }

    return error;
  };

  const { validateForm } = useFormikContext();
  useEffect(() => {
    if (useRegex) {
      validateForm();
    }
  }, [useRegex, validateForm]);

  return (
    <div
      className={classNames(
        "col-span-12",
        hidden ? "hidden" : "",
        columns ? `sm:col-span-${columns}` : ""
      )}
    >
      {label && (
        <label
          htmlFor={name}
          className={classNames(
            tooltip ? "z-10" : "",
            "flex ml-px text-xs font-bold text-gray-800 dark:text-gray-100 uppercase tracking-wide"
          )}
        >
          {tooltip ? (
            <DocsTooltip label={label}>{tooltip}</DocsTooltip>
          ) : label}
        </label>
      )}
      <Field
        name={name}
        validate={useRegex && validateRegexp}
      >
        {({ field, meta }: FieldProps) => (
          <div className="relative">

            <TextareaAutosize
              {...field}
              id={name}
              maxRows={10}
              defaultValue={defaultValue}
              autoComplete={autoComplete}
              className={classNames(
                useRegex && meta.error
                  ? "border-red-500 focus:ring-red-500 focus:border-red-500"
                  : "border-gray-300 dark:border-gray-700 focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500",
                disabled
                  ? "bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed"
                  : "bg-gray-100 dark:bg-gray-815 dark:text-gray-100",
                useRegex
                  ? "pr-10"
                  : "",
                "mt-1 block w-full dark:text-gray-100 rounded-md"
              )}
              placeholder={placeholder}
              disabled={disabled}
            />

            {useRegex && (
              <div className="relative">
                <div className="flex float-right items-center">
                  {!meta.error ? (
                    <CheckCircleIcon className="h-8 w-8 mb-2.5 pl-1 text-green-500 right-2 absolute transform -translate-y-1/2" aria-hidden="true" style={{ overflow: "hidden" }} />
                  ) : (
                    <XCircleIcon className="h-8 w-8 mb-2.5 pl-1 text-red-500 right-2 absolute transform -translate-y-1/2" aria-hidden="true" style={{ overflow: "hidden" }} />
                  )}
                </div>
              </div>
            )}
          </div>
        )}
      </Field>

    </div>
  );
};

interface TextAreaProps {
  name: string;
  defaultValue?: string;
  label?: string;
  placeholder?: string;
  columns?: COL_WIDTHS;
  rows?: number;
  autoComplete?: string;
  hidden?: boolean;
  disabled?: boolean;
  tooltip?: JSX.Element;
}

export const TextArea = ({
  name,
  defaultValue,
  label,
  placeholder,
  columns,
  rows,
  autoComplete,
  hidden,
  tooltip,
  disabled
}: TextAreaProps) => (
  <div
    className={classNames(
      "col-span-12",
      hidden ? "hidden" : "",
      columns ? `sm:col-span-${columns}` : ""
    )}
  >
    {label && (
      <label htmlFor={name} className="flex ml-px text-xs font-bold text-gray-800 dark:text-gray-100 uppercase tracking-wide">
        {tooltip ? (
          <DocsTooltip label={label}>{tooltip}</DocsTooltip>
        ) : label}
      </label>
    )}
    <Field name={name}>
      {({
        field,
        meta
      }: FieldProps) => (
        <div>
          <textarea
            {...field}
            id={name}
            rows={rows}
            defaultValue={defaultValue}
            autoComplete={autoComplete}
            className={classNames(
              meta.touched && meta.error
                ? "border-red-500 focus:ring-red-500 focus:border-red-500"
                : "border-gray-300 dark:border-gray-700 focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500",
              disabled
                ? "bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed"
                : "bg-gray-100 dark:bg-gray-815 dark:text-gray-100",
              "mt-1 block border w-full dark:text-gray-100 rounded-md"
            )}
            placeholder={placeholder}
            disabled={disabled}
          />

          {meta.touched && meta.error && (
            <p className="error text-sm text-red-600 mt-1">* {meta.error}</p>
          )}
        </div>
      )}
    </Field>
  </div>
);

interface TextAreaAutoResizeProps {
  name: string;
  defaultValue?: string;
  label?: string;
  placeholder?: string;
  columns?: COL_WIDTHS;
  rows?: number;
  autoComplete?: string;
  hidden?: boolean;
  disabled?: boolean;
  tooltip?: JSX.Element;
  className?: string;
}

export const TextAreaAutoResize = ({
  name,
  defaultValue,
  label,
  placeholder,
  columns,
  rows,
  autoComplete,
  hidden,
  tooltip,
  disabled,
  className = ""
}: TextAreaAutoResizeProps) => (
  <div
    className={classNames(
      className,
      "col-span-12",
      hidden ? "hidden" : "",
      columns ? `sm:col-span-${columns}` : ""
    )}
  >
    {label && (
      <label htmlFor={name} className="flex ml-px text-xs font-bold text-gray-800 dark:text-gray-100 uppercase tracking-wide">
        {tooltip ? (
          <DocsTooltip label={label}>{tooltip}</DocsTooltip>
        ) : label}
      </label>
    )}
    <Field name={name}>
      {({
        field,
        meta
      }: FieldProps) => (
        <div>
          <TextareaAutosize
            {...field}
            id={name}
            rows={rows}
            maxRows={10}
            defaultValue={defaultValue}
            autoComplete={autoComplete}
            className={classNames(
              meta.touched && meta.error
                ? "border-red-500 focus:ring-red-500 focus:border-red-500"
                : "border-gray-300 dark:border-gray-700 focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500",
              disabled
                ? "bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed"
                : "bg-gray-100 dark:bg-gray-815 dark:text-gray-100",
              "mt-1 block w-full dark:text-gray-100 rounded-md"
            )}
            placeholder={placeholder}
            disabled={disabled}
          />

          {meta.touched && meta.error && (
            <p className="error text-sm text-red-600 mt-1">* {meta.error}</p>
          )}
        </div>
      )}
    </Field>
  </div>
);


interface PasswordFieldProps {
  name: string;
  label?: string;
  placeholder?: string;
  columns?: COL_WIDTHS;
  autoComplete?: string;
  defaultValue?: string;
  help?: string;
  required?: boolean;
  tooltip?: JSX.Element;
}

export const PasswordField = ({
  name,
  label,
  placeholder,
  defaultValue,
  columns,
  autoComplete,
  help,
  tooltip,
  required
}: PasswordFieldProps) => {
  const [isVisible, toggleVisibility] = useToggle(false);

  return (
    <div
      className={classNames(
        "col-span-12",
        columns ? `sm:col-span-${columns}` : ""
      )}
    >
      {label && (
        <label htmlFor={name} className="flex ml-px text-xs font-bold text-gray-800 dark:text-gray-100 uppercase tracking-wide">
          {tooltip ? (
            <DocsTooltip label={label}>{tooltip}</DocsTooltip>
          ) : (
            label
          )}
          {required && <span className="text-red-500">*</span>}
        </label>
      )}
      <div>
        <Field name={name} defaultValue={defaultValue}>
          {({
            field,
            meta
          }: FieldProps) => (
            <>
              <div className="sm:col-span-2 relative">
                <input
                  {...field}
                  id={name}
                  type={isVisible ? "text" : "password"}
                  autoComplete={autoComplete}
                  className={classNames(
                    meta.touched && meta.error
                      ? "border-red-500 focus:ring-red-500 focus:border-red-500"
                      : "border-gray-300 dark:border-gray-700 focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500",
                    "mt-1 block w-full rounded-md bg-gray-100 dark:bg-gray-815 dark:text-gray-100"
                  )}
                  placeholder={placeholder}
                />

                <div className="absolute inset-y-0 right-0 px-3 flex items-center" onClick={toggleVisibility}>
                  {!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />
                    : <EyeSlashIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
                </div>
              </div>
              {help && (
                <p className="mt-2 text-sm text-gray-500" id="email-description">{help}</p>
              )}

              {meta.touched && meta.error && (
                <p className="error text-sm text-red-600 mt-1">* {meta.error}</p>
              )}
            </>
          )}
        </Field>
      </div>
    </div>
  );
};

interface NumberFieldProps {
  name: string;
  label?: string;
  placeholder?: string;
  step?: number;
  disabled?: boolean;
  required?: boolean;
  min?: number;
  max?: number;
  tooltip?: JSX.Element;
  className?: string;
  isDecimal?: boolean;
}

export const NumberField = ({
  name,
  label,
  placeholder,
  step,
  min,
  max,
  tooltip,
  disabled,
  required,
  isDecimal,
  className = ""
}: NumberFieldProps) => (
  <div className={classNames(className, "col-span-12 sm:col-span-6")}>
    <label
      htmlFor={name}
      className="flex ml-px text-xs font-bold text-gray-800 dark:text-gray-100 uppercase tracking-wide"
    >
      {tooltip ? (
        <DocsTooltip label={label}>{tooltip}</DocsTooltip>
      ) : label}
    </label>

    <Field name={name} type="number">
      {({ field, meta, form }: FieldProps<number>) => (
        <div className="sm:col-span-2">
          <input
            type="number"
            {...field}
            step={step}
            min={min}
            max={max}
            inputMode={isDecimal ? "decimal" : "numeric"}
            required={required}
            className={classNames(
              meta.touched && meta.error
                ? "border-red-500 focus:ring-red-500 focus:border-red-500"
                : "border-gray-300 dark:border-gray-700 focus:ring-blue-500 dark:focus:ring-blue-500 focus:border-blue-500 dark:focus:border-blue-500",
              "mt-1 block w-full border rounded-md",
              disabled
                ? "bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed"
                : "bg-gray-100 dark:bg-gray-815 dark:text-gray-100"
            )}
            placeholder={placeholder}
            disabled={disabled}
            onChange={event => {
              // safeguard and validation if user removes the number
              // it will then set 0 by default. Formik can't handle this properly
              if (event.target.value == "") {
                form.setFieldValue(field.name, 0);
                return;
              }
              if (isDecimal) {
                form.setFieldValue(field.name, parseFloat(event.target.value));
              } else {
                form.setFieldValue(field.name, parseInt(event.target.value));
              }
            }}
            onWheel={(event) => {
              if (event.currentTarget === document.activeElement) {
                event.currentTarget.blur();
                setTimeout(() => event.currentTarget.focus(), 0);
              }
            }}
          />
          {meta.touched && meta.error && (
            <div className="error">{meta.error}</div>
          )}
        </div>
      )}
    </Field>
  </div>
);
