/**
 * External dependencies
 */
import DOMPurify from "dompurify";
import {
  isBoolean,
  isNil,
  isObject,
  isString,
  isArray,
  isUndefined,
} from "lodash";

/**
 * WordPress dependencies
 */
import { sprintf, __ } from "@wordpress/i18n";
import {
  TextControl,
  TextareaControl,
  SelectControl,
  ToggleControl,
  BaseControl,
  FormTokenField,
  __experimentalUnitControl as UnitControl,
} from "@wordpress/components";
import { useState } from "@wordpress/element";

/**
 * Internal dependencies
 */
import {
  RepeaterGroupControl,
  LabelControl,
  ColorGradientDropdown,
} from "sdk/components";
import { useColorGradient, getColorCSSValue } from "sdk/utils";
import CustomMediaUpload from "../custom-media-upload";
import { GroupItemToggle } from "../group-item-toggle";
import { HelpLink } from "../help-link";
import { handlePatternInput } from "../../utils";
import { labels } from "../../utils/labels";
import { shareLabels } from "../../utils/shared-labels";

import { AttributesControlStyled, BlockAttributesStyled } from "./styles";

export const AttributesControl = (props) => {
  const {
    label,
    values,
    onChange,
    isDefinitionMode = false,
    isEditable = true,
    openStates = {},
    setOpenStates,
    index,
  } = props;

  const valueLabel = __("Value", "content-blocks-builder");

  const fields = [
    {
      name: "attributeType",
      label: __("Attribute type", "content-blocks-builder"),
      placeholder: "",
      required: true,
      options: [
        {
          value: "data",
          label: __("Data Attribute", "content-blocks-builder"),
        },
        { value: "var", label: __("CSS Variable", "content-blocks-builder") },
        {
          value: "aria",
          label: __("Aria Attribute", "content-blocks-builder"),
        },
        {
          value: "custom",
          label: __("Custom Attribute", "content-blocks-builder"),
        },
      ],
    },
    {
      name: "name",
      label: shareLabels.name,
      placeholder: "my-attribute-name",
      required: true,
      autoComplete: "off",
      pattern: "^[a-zA-Z]([a-zA-Z0-9])*([-]([a-zA-Z][a-zA-Z0-9]*)*)*",
      help: __(
        "Use only lowercase letters, numbers, or dashes (-) for the attribute name.",
        "content-blocks-builder",
      ),
    },
    {
      name: "type",
      label: __("Data type", "content-blocks-builder"),
      placeholder: "",
      required: true,
      options: [
        { value: "text", label: __("Text", "content-blocks-builder") },
        { value: "number", label: __("Number", "content-blocks-builder") },
        { value: "boolean", label: __("Boolean", "content-blocks-builder") },
        { value: "select", label: __("Select", "content-blocks-builder") },
        { value: "unit", label: __("Unit", "content-blocks-builder") },
        { value: "color", label: __("Color", "content-blocks-builder") },
        {
          value: "background",
          label: __("Background", "content-blocks-builder"),
        },
        { value: "image", label: __("Image", "content-blocks-builder") },
        { value: "video", label: __("Video", "content-blocks-builder") },
        { value: "json", label: __("JSON", "content-blocks-builder") },
        { value: "label", label: shareLabels.label },
      ],
    },
    {
      name: "multiple",
      label: __("Multiple ", "content-blocks-builder"),
    },
    {
      name: "choices",
      label: __("Choices ", "content-blocks-builder"),
      help: __(
        "Enter each choice on a new line (eg. Red). For more control, you may specify both a value and a label like this: 'red : Red'.",
        "content-blocks-builder",
      ),
    },
    {
      name: "rows",
      label: __("Rows ", "content-blocks-builder"),
    },
    {
      name: "label",
      label: shareLabels.label,
      placeholder: __("Field label ", "content-blocks-builder"),
      required: true,
      autoComplete: "off",
    },
  ];

  const valueItem = {
    name: "value",
    label: isDefinitionMode ? labels.default : valueLabel,
    placeholder: "",
    required: true,
    autoComplete: "off",
    rows: 4,
  };

  const helpItem = {
    name: "help",
    label: __("Help text", "content-blocks-builder"),
    placeholder: __(
      "Input the help text for the field",
      "content-blocks-builder",
    ),
    autoComplete: "off",
    rows: 4,
  };

  // Change the help text position based on the edit mode
  if (isEditable) {
    fields.push(helpItem);
    fields.push(valueItem);
  } else {
    fields.push(valueItem);
    fields.push(helpItem);
  }

  if (isDefinitionMode) {
    fields.push({
      name: "isReadonly",
      label: __("Is ready-only", "content-blocks-builder"),
    });
    fields.push({
      name: "initialOpen",
      label: __(
        "Show the input control at the start",
        "content-blocks-builder",
      ),
    });
  }

  const renderControl = ({
    name,
    label,
    value,
    onChange,
    help = "",
    options,
    values,
    fields,
    pattern,
    ...otherProps
  }) => {
    const {
      type: controlType = "text",
      attributeType,
      name: attrName,
      label: fieldLabel,
      isReadonly,
    } = values ?? {};

    let helpLabel = __("HTML attribute name:", "content-blocks-builder");
    let helpCode = `${attributeType}-${attrName}`;

    switch (attributeType) {
      case "var":
        helpLabel = __("CSS variable name: ", "content-blocks-builder");
        helpCode = `--bb-attr--${attrName}`;
        break;
      case "custom":
        helpCode = attrName;
        break;
    }

    const attributeHelp = (
      <span>
        {helpLabel}
        <code>{helpCode}</code>
      </span>
    );

    switch (name) {
      case "name":
        return isEditable ? (
          <TextControl
            label={label}
            value={value}
            onChange={handlePatternInput(pattern, onChange)}
            help={
              <>
                {help}
                <br />
                {attributeHelp}
              </>
            }
            {...otherProps}
          />
        ) : null;

      case "type":
      case "attributeType":
        return isEditable ? (
          <SelectControl
            label={label}
            value={value}
            onChange={onChange}
            options={options}
            help={help}
            {...otherProps}
          />
        ) : null;

      case "choices":
        return isEditable && controlType === "select" ? (
          <TextareaControl
            label={label}
            value={value}
            onChange={onChange}
            rows={6}
            help={help}
            {...otherProps}
          />
        ) : null;

      case "multiple":
        return isEditable && controlType === "select" ? (
          <ToggleControl
            label={label}
            checked={isBoolean(value) ? value : false}
            onChange={onChange}
            {...otherProps}
          />
        ) : null;

      case "rows":
        return isEditable && controlType === "text" ? (
          <SelectControl
            label={label}
            value={value ?? 4}
            onChange={onChange}
            options={[...Array(10)].map((_, i) => ({
              value: i + 1,
              label: i + 1,
            }))}
          />
        ) : null;

      // Label only available in definition mode
      case "label":
        return isDefinitionMode ? (
          <TextControl
            label={label}
            value={value}
            onChange={onChange}
            help={help}
            {...otherProps}
          />
        ) : null;

      case "help":
        return isDefinitionMode ? (
          <div className="attribute-help-control">
            <TextareaControl
              label={label}
              value={isString(value) ? value : ""}
              onChange={onChange}
              {...otherProps}
            />
          </div>
        ) : (
          <>
            {value && (
              <div
                className="attribute-help"
                dangerouslySetInnerHTML={{
                  __html: isString(value) ? DOMPurify.sanitize(value) : "",
                }}
              />
            )}
          </>
        );

      case "value":
        help = isEditable ? help : attributeHelp;
        label = isEditable ? label : null;

        // Show only value for readonly attributes in the input mode
        if (isReadonly && !isDefinitionMode) {
          let valueString = "";
          switch (controlType) {
            case "color":
            case "background":
              valueString = value ? getColorCSSValue(value) : "";
              break;

            case "image":
            case "video":
              valueString = JSON.stringify(value);
              break;

            default:
              valueString = value + "";
          }

          return (
            <div className="attribute-value-control">
              <BaseControl label={valueLabel} />
              <code className="readonly-value">{valueString}</code>
              <BaseControl help={help} />
            </div>
          );
        }

        switch (controlType) {
          case "boolean":
            return (
              <div className="attribute-value-control">
                <ToggleControl
                  label={isEditable ? label : fieldLabel}
                  checked={isBoolean(value) ? value : false}
                  onChange={onChange}
                  {...otherProps}
                />
                <BaseControl help={help} />
              </div>
            );

          case "color":
            const color =
              isObject(value) && (!isNil(value?.value) || !isNil(value?.slug))
                ? value
                : {};

            const [onColorChange] = useColorGradient(color, onChange);
            return (
              <div className="attribute-value-control">
                <BaseControl label={label} />
                <ColorGradientDropdown
                  enableAlpha={true}
                  settings={[
                    {
                      label: __("Color", "content-blocks-builder"),
                      onColorChange: onColorChange,
                      colorValue: color?.value,
                      gradients: null,
                    },
                  ]}
                />
                <BaseControl help={help} />
              </div>
            );

          case "background":
            const background =
              (isObject(value) && !isNil(value?.value)) ||
              !isNil(value?.slug) ||
              !isNil(value?.gradientSlug) ||
              !isNil(value?.gradientValue)
                ? value
                : {};

            const [onBackgroundColorChange, onGradientChange] =
              useColorGradient(background, onChange);
            return (
              <div className="attribute-value-control">
                <BaseControl label={label} />
                <ColorGradientDropdown
                  enableAlpha={true}
                  settings={[
                    {
                      label: __(
                        "Color with gradient",
                        "content-blocks-builder",
                      ),
                      onColorChange: onBackgroundColorChange,
                      colorValue: background?.value,
                      onGradientChange: onGradientChange,
                      gradientValue: background?.gradientValue,
                    },
                  ]}
                />
                <BaseControl help={help} />
              </div>
            );

          case "image":
            const media = isObject(value) && value?.url ? value : { url: "" };
            return (
              <div className="attribute-value-control">
                <BaseControl label={label} />
                <CustomMediaUpload
                  mediaType="image"
                  media={media}
                  label={__("Set image", "content-blocks-builder")}
                  inputLabel={__("Image URL", "content-blocks-builder")}
                  inputPlaceholder="https://example.com/image-file-name.jpg"
                  changeMediaLabel={__(
                    "Change image",
                    "content-blocks-builder",
                  )}
                  removeMediaLabel={__(
                    "Remove image",
                    "content-blocks-builder",
                  )}
                  onMediaChange={(newMedia) =>
                    onChange({
                      ...newMedia,
                      customAlt: media?.customAlt
                        ? media.customAlt
                        : newMedia?.alt,
                    })
                  }
                  renderPreview={(media) => (
                    <img
                      src={media?.url}
                      alt={__("Media preview", "content-blocks-builder")}
                    />
                  )}
                />
                <BaseControl help={help} />
              </div>
            );

          case "video":
            const video = isObject(value) && value?.url ? value : { url: "" };
            return (
              <div className="attribute-value-control">
                <BaseControl label={label} />
                <CustomMediaUpload
                  mediaType="video"
                  media={video}
                  label={__("Set video", "content-blocks-builder")}
                  inputLabel={__("Video URL", "content-blocks-builder")}
                  inputPlaceholder="https://example.com/video-file-name.mp4"
                  changeMediaLabel={__(
                    "Change video",
                    "content-blocks-builder",
                  )}
                  removeMediaLabel={__(
                    "Remove video",
                    "content-blocks-builder",
                  )}
                  onMediaChange={(newVideo) =>
                    onChange({
                      ...newVideo,
                      customAlt: video?.customAlt
                        ? video.customAlt
                        : newVideo?.alt,
                    })
                  }
                  renderPreview={(video) => (
                    <video src={video?.url} autoPlay={true} loop muted={true} />
                  )}
                />
                <BaseControl help={help} />
              </div>
            );

          case "number":
            return (
              <div className="attribute-value-control">
                <TextControl
                  label={label}
                  value={value}
                  onChange={onChange}
                  type={controlType}
                  {...otherProps}
                />
                <BaseControl help={help} />
              </div>
            );

          case "select":
            if (!values?.choices) {
              return null;
            }
            const options = values?.choices.split("\n").map((line) => {
              const [value, label = value] = line.split(" : ");
              return { value: value.trim(), label: label.trim() };
            });
            return (
              <div className="attribute-value-control">
                {values?.multiple ? (
                  <FormTokenField
                    label={label}
                    value={
                      value
                        ? isArray(value)
                          ? value
                          : isString(value)
                          ? [value]
                          : []
                        : []
                    }
                    onChange={onChange}
                    suggestions={options.map(({ value }) => value)}
                    __experimentalExpandOnFocus={true}
                    __experimentalRenderItem1={({ item }) => {
                      const { label = "" } =
                        sortingSuggestiongs.find(
                          ({ value }) => item === value,
                        ) ?? {};

                      return label ? label : item;
                    }}
                    {...otherProps}
                  />
                ) : (
                  <SelectControl
                    label={label}
                    value={value}
                    onChange={onChange}
                    options={options}
                    {...otherProps}
                  />
                )}
                <BaseControl help={help} />
              </div>
            );

          case "json":
            return (
              <div className="attribute-value-control">
                <TextareaControl
                  label={label}
                  value={isString(value) ? value : ""}
                  onChange={onChange}
                  rows={6}
                />
                <BaseControl
                  help={
                    <>
                      {__(
                        "Please make sure the value is a valid JSON string.",
                        "content-blocks-builder",
                      )}{" "}
                      <HelpLink
                        href="https://jsonlint.com"
                        label={__(
                          "Validate JSON data",
                          "content-blocks-builder",
                        )}
                      />
                      {help}
                    </>
                  }
                />
              </div>
            );

          case "unit":
            return (
              <div className="attribute-value-control">
                <UnitControl
                  label={label}
                  value={isString(value) ? value : ""}
                  onChange={onChange}
                  {...otherProps}
                />
                <BaseControl help={help} />
              </div>
            );

          case "label":
            return (
              <>
                {isEditable ? (
                  <div className="attribute-value-control">
                    <TextareaControl
                      label={label}
                      value={isString(value) ? value : ""}
                      onChange={onChange}
                      rows={10}
                    />
                    <BaseControl help={help} />
                  </div>
                ) : (
                  <div
                    dangerouslySetInnerHTML={{
                      __html: isString(value) ? DOMPurify.sanitize(value) : "",
                    }}
                  />
                )}
              </>
            );

          default:
            let rows = parseInt(values?.rows ?? 4);
            if (isNaN(rows)) {
              rows = 4;
            }
            return (
              <div className="attribute-value-control">
                {rows === 1 ? (
                  <TextControl
                    label={label}
                    value={isString(value) ? value : ""}
                    onChange={onChange}
                    {...otherProps}
                  />
                ) : (
                  <TextareaControl
                    label={label}
                    value={isString(value) ? value : ""}
                    onChange={onChange}
                    {...otherProps}
                    rows={rows}
                  />
                )}
                <BaseControl help={help} />
              </div>
            );
        }

      case "initialOpen":
        return (
          <ToggleControl
            label={label}
            checked={value}
            onChange={onChange}
            help={help}
            className="attribute-toggle-control"
          />
        );

      case "isReadonly":
        return (
          <ToggleControl
            label={label}
            checked={value}
            onChange={onChange}
            help={help}
            className="attribute-toggle-control"
          />
        );

      default:
        break;
    }

    return null;
  };

  const initialOpenState = !isUndefined(openStates[`item${index}`])
    ? openStates[`item${index}`]
    : !values?.name || (!isEditable && values?.type === "label");

  const itemLabel = isEditable ? (
    <>
      {label} {values?.name && <code>{values.name}</code>}
    </>
  ) : (
    values.label
  );

  return (
    <>
      {setOpenStates ? (
        <GroupItemToggle
          isOpen={initialOpenState}
          setIsOpen={(newState) => {
            setOpenStates({ ...openStates, [`item${index}`]: newState });
          }}
          label={itemLabel}
        />
      ) : (
        <>
          {values?.type !== "boolean" && (
            <BaseControl.VisualLabel>{itemLabel}</BaseControl.VisualLabel>
          )}
        </>
      )}
      <AttributesControlStyled
        fields={fields}
        values={values}
        renderLabel={({ label }) => {
          return <LabelControl label={label} isResponsive={false} />;
        }}
        renderControl={renderControl}
        isLinkedGroup={false}
        onChange={onChange}
        hideHeader={true}
        className="custom-attributes-group"
      />
    </>
  );
};

/**
 * Render a repeater control of custom attributes
 * isDefinitionMode: is for adding read-only control only when editing block
 * isEditable: is for adding new custom attributes or not
 *
 * @param {Object} props
 * @returns
 */
export const CustomAttributes = (props) => {
  const { label, startIndex = 0, isDefinitionMode, values } = props;
  const renderControl = ({
    value,
    onChange,
    index,
    isEditable,
    openStates = {},
    setOpenStates,
  }) => (
    <AttributesControl
      label={sprintf(
        __("Attribute %d", "content-blocks-builder"),
        index + startIndex + 1,
      )}
      values={value}
      onChange={onChange}
      isEditable={isEditable}
      openStates={openStates}
      setOpenStates={setOpenStates}
      index={index}
      isDefinitionMode={isDefinitionMode}
    />
  );

  const defaultItemValue = {
    attributeType: "data",
    name: "",
    type: "text",
    value: "",
  };

  const [openStates, setOpenStates] = useState(() =>
    !isDefinitionMode && values && values?.length
      ? values.reduce((acc, { initialOpen }, index) => {
          acc[`item${index}`] = initialOpen;
          return acc;
        }, {})
      : {},
  );

  return (
    <div className="custom-attributes-control">
      {label && (
        <LabelControl label={label} isResponsive={false} isBold={true} />
      )}
      <RepeaterGroupControl
        {...props}
        addItemLabel={__("Add new attribute", "content-blocks-builder")}
        removeItemLabel={__("Remove attribute", "content-blocks-builder")}
        renderControl={renderControl}
        defaultItemValue={defaultItemValue}
        onMove={(index, oldIndex) => {
          let newStates = {
            [`item${index}`]: openStates[`item${oldIndex}`] ?? false,
            [`item${oldIndex}`]: openStates[`item${index}`] ?? false,
          };

          setOpenStates({ ...openStates, ...newStates });
        }}
        onCreate={(index) => {
          setOpenStates({ ...openStates, [`item${index}`]: true });
        }}
        onRemove={(index) => {
          if (!(openStates[`item${index + 1}`] ?? false)) {
            setOpenStates({ ...openStates, [`item${index}`]: null });
          }
        }}
        openStates={openStates}
        setOpenStates={setOpenStates}
      />
    </div>
  );
};

export const BlockAttributes = ({
  attributes,
  enableCustomAttributes,
  onChangeAttributes,
  onChangeEnableCustomAttributes,
  label,
}) => {
  return (
    <BlockAttributesStyled>
      <LabelControl label={label} isResponsive={false} isBold={true} />
      <div className="block-custom-attributes">
        <CustomAttributes
          values={attributes}
          onChange={onChangeAttributes}
          isDefinitionMode={true}
        />
        <ToggleControl
          checked={enableCustomAttributes}
          label={__("Enable creating new attributes", "content-blocks-builder")}
          onChange={onChangeEnableCustomAttributes}
        />
      </div>
    </BlockAttributesStyled>
  );
};
