import React, { useState, useRef } from "react";
import classnames from "classnames";

import Button from "components/buttons/Button";
import Card from "components/Card";
import { GraphicNames } from "components/graphics";
import Icon from "components/Icon";
import Graphic from "components/Graphic";
import FileDetails from "components/FileDetails";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";
import TooltipWrapper from "components/TooltipWrapper";

const baseClass = "file-uploader";

export type ISupportedGraphicNames = Extract<
  GraphicNames,
  | "file-configuration-profile"
  | "file-sh"
  | "file-ps1"
  | "file-py"
  | "file-script"
  | "file-pdf"
  | "file-pkg"
  | "file-p7m"
  | "file-pem"
  | "file-vpp"
  | "file-png"
>;

interface IFileUploaderProps {
  label?: React.ReactNode;
  graphicName: ISupportedGraphicNames | ISupportedGraphicNames[];
  message: React.ReactNode;
  title?: string;
  /** allow error state within the file uploader, as opposed to on its label */
  internalError?: string;
  additionalInfo?: string;
  /** Controls the loading spinner on the upload button */
  isLoading?: boolean;
  /** Disables the upload button */
  disabled?: boolean;
  /** A comma separated string of one or more file types accepted to upload.
   * This is the same as the html accept attribute.
   * https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
   */
  accept?: string;
  /** The text to display on the upload button
   * @default "Upload"
   */
  buttonMessage?: string;
  className?: string;
  /** renders the button to open the file uploader to appear as a button or
   * a link.
   * @default "button"
   */
  buttonType?: "button" | "brand-inverse-icon";
  /** renders a tooltip for the button. If `gitopsCompatible` is set to `true`
   * this tooltip will not be rendered if gitops mode is enabled. */
  buttonTooltip?: React.ReactNode;
  onFileUpload: (files: FileList | null) => void;
  /** renders the current file with the edit pencil button */
  canEdit?: boolean;
  /** renders a custom editor for the current file replacing the edit pencil button */
  customEditor?: () => React.ReactNode;
  /** renders the current file with the delete trash button */
  onDeleteFile?: () => void;
  /** if provided, will be called when the button is clicked
   * instead of opening the file selector. Useful if you want to
   * show the file selector UI but handle the file selection
   * in a modal.
   */
  onButtonClick?: () => void;
  fileDetails?: {
    name: string;
    description?: React.ReactNode;
  };
  /** Indicates that this file uploader deals with an entity that can be managed by GitOps, and so should be disabled when gitops mode is enabled */
  gitopsCompatible?: boolean;
  /** Whether or not GitOpsMode is enabled. Has no effect if `gitopsCompatible` is false */
  gitOpsModeEnabled?: boolean;
}

/**
 * A component that encapsulates the UI for uploading a file and a file selected.
 */
export const FileUploader = ({
  label,
  graphicName: graphicNames,
  message,
  title,
  internalError,
  additionalInfo,
  isLoading = false,
  disabled = false,
  accept,
  className,
  buttonMessage = "Upload",
  buttonType = "button",
  buttonTooltip,
  onButtonClick,
  onFileUpload,
  canEdit = false,
  customEditor,
  onDeleteFile,
  fileDetails,
  gitopsCompatible = false,
  gitOpsModeEnabled = false,
}: IFileUploaderProps) => {
  const [isFileSelected, setIsFileSelected] = useState(!!fileDetails);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const classes = classnames(baseClass, className, {
    [`${baseClass}__file-preview`]: isFileSelected,
    [`${baseClass}__error`]: !!internalError,
  });
  const buttonVariant =
    buttonType === "button" ? "default" : "brand-inverse-icon";

  const triggerFileInput = () => {
    fileInputRef.current?.click();
  };

  const onFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
    const target = e.currentTarget;
    // Ensure target is the expected input element to prevent DOM manipulation
    if (target && target.type === "file") {
      const files = target.files;
      onFileUpload(files);
      setIsFileSelected(true);

      if (fileInputRef.current) {
        fileInputRef.current.value = "";
      }
    }
  };

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter") {
      e.preventDefault();
      triggerFileInput();
    }
  };

  const renderLabel = () => {
    return label ? (
      <div className={`${baseClass}__label form-field__label`}>{label}</div>
    ) : null;
  };
  const renderGraphics = () => {
    const graphicNamesArr =
      typeof graphicNames === "string" ? [graphicNames] : graphicNames;
    return graphicNamesArr.map((graphicName) => (
      <Graphic
        key={`${graphicName}-graphic`}
        className={`${baseClass}__graphic`}
        name={graphicName}
      />
    ));
  };

  const renderUploadButton = () => {
    let buttonMarkup = (
      <>
        {buttonMessage}
        {buttonType === "brand-inverse-icon" && (
          <Icon color="core-fleet-green" name="upload" />
        )}
      </>
    );
    // If we want to actual do file uploading, wrap in a label that
    // references the hidden file input. Otherwise just use a span.
    if (!onButtonClick) {
      buttonMarkup = <label htmlFor="upload-file">{buttonMarkup}</label>;
    } else {
      buttonMarkup = <span>{buttonMarkup}</span>;
    }
    // the gitops mode tooltip wrapper takes presedence over other button
    // renderings
    if (gitopsCompatible) {
      return (
        <GitOpsModeTooltipWrapper
          tipOffset={8}
          renderChildren={(disableChildren) => (
            <TooltipWrapper
              className={`${baseClass}__manual-install-tooltip`}
              tipContent={buttonTooltip}
              disableTooltip={disableChildren || !buttonTooltip}
              position="top"
              showArrow
              underline={false}
            >
              <Button
                className={`${baseClass}__upload-button`}
                variant={buttonVariant}
                isLoading={isLoading}
                disabled={disabled || disableChildren}
                customOnKeyDown={!onButtonClick ? handleKeyDown : undefined}
                onClick={onButtonClick || undefined}
                tabIndex={0}
              >
                {buttonMarkup}
              </Button>
            </TooltipWrapper>
          )}
        />
      );
    }

    return (
      <TooltipWrapper
        className={`${baseClass}__upload-button`}
        position="top"
        tipContent={buttonTooltip}
        underline={false}
        showArrow
        disableTooltip={!buttonTooltip}
      >
        <Button
          className={`${baseClass}__upload-button`}
          variant={buttonVariant}
          isLoading={isLoading}
          disabled={disabled}
          customOnKeyDown={!onButtonClick ? handleKeyDown : undefined}
          onClick={onButtonClick || undefined}
          tabIndex={0}
        >
          {buttonMarkup}
        </Button>
      </TooltipWrapper>
    );
  };

  const renderTitle = () => {
    if (internalError) {
      return (
        <div className={`${baseClass}__internal-error`}>{internalError}</div>
      );
    }
    if (title) {
      return <div className={`${baseClass}__title`}>{title}</div>;
    }
    return null;
  };

  const renderFileUploader = () => {
    return (
      <div className={`${baseClass}__content-wrapper`}>
        <div className={`${baseClass}__outer`}>
          <div className={`${baseClass}__inner`}>
            <div className={`${baseClass}__graphics`}>{renderGraphics()}</div>
            {renderTitle()}
            <p className={`${baseClass}__message`}>{message}</p>
            {additionalInfo && (
              <p className={`${baseClass}__additional-info`}>
                {additionalInfo}
              </p>
            )}
          </div>
          {renderUploadButton()}
          {/* If onButtonClick is provided, we're not actually uploading files here. */}
          {!onButtonClick && (
            <input
              ref={fileInputRef}
              accept={accept}
              id="upload-file"
              type="file"
              onChange={onFileSelect}
              className="file-input-visually-hidden"
            />
          )}
        </div>
      </div>
    );
  };

  return (
    <div className={`${baseClass}__wrapper form-field`}>
      {renderLabel()}
      <Card color="grey" className={classes}>
        {fileDetails ? (
          <FileDetails
            graphicNames={graphicNames}
            fileDetails={fileDetails}
            canEdit={canEdit}
            customEditor={customEditor}
            onDeleteFile={onDeleteFile}
            onFileSelect={onFileSelect}
            accept={accept}
            gitopsCompatible={gitopsCompatible}
            gitOpsModeEnabled={gitOpsModeEnabled}
          />
        ) : (
          renderFileUploader()
        )}
      </Card>
    </div>
  );
};

export default FileUploader;
