import { BORDER_RADIUS, COLORS, FlipFlop, IconLoading, ProgressBar } from "@heydovetail/ui-components";
import React from "react";
import { styled } from "typestyle-react";
import { AttachmentPreviewImage, AttachmentService, AttachmentUpload } from "../../AttachmentService";
import { Attachment } from "../../util/attrs";
import { Box, box } from "../../util/box";
import { AttachmentActions } from "../Shared/AttachmentActions";
import { AttachmentPreviewSubscribe } from "../Shared/AttachmentPreviewSubscribe";
import { AttachmentUploadSubscribe } from "../Shared/AttachmentUploadSubscribe";
import { ResponsiveImage } from "../Shared/ResponsiveImage";
import { SelectionMask } from "../Shared/SelectionMask";

const minSizeForIcon = 32;
const minSizeForMeatballAlignRight = 160;
const minSizeForActionsOutside = 80;

export interface Props {
  attachment: Attachment;
  attachmentService: AttachmentService;
  editable: boolean;
  onHidePreview: () => void;
  onRemove: () => void;
  style?: "active" | "selected";
}

// When an image is uploaded and doesn't include size information, it defaults
// to this.
const unavailableUnknownSizeFallback = box(300, 300);

interface ContainerProps {
  attachment: Attachment;
  attachmentService: AttachmentService;
  editable: boolean;
  onHidePreview: () => void;
  onRemove: () => void;
  size?: Box;
  style?: "active" | "selected";
  progressStyle?: "light" | "dark";
}

class ImageContainer extends React.PureComponent<ContainerProps> {
  public render() {
    const { props } = this;
    const { progressStyle = "light", size, style } = props;
    return (
      <AttachmentUploadSubscribe {...props}>
        {attachmentUpload => (
          <FlipFlop>
            {({ set: setMenuOpenTo, active: isMenuOpen }) => (
              <FlipFlop>
                {({ setOn: setHovered, setOff: setNotHovered, active: isHovered }) => (
                  <HoverContainer onMouseEnter={setHovered} onMouseLeave={setNotHovered}>
                    <RelativeContainer>
                      <InnerContainer styled={{ style }}>{this.props.children}</InnerContainer>
                      <UploadStateMask progressStyle={progressStyle} attachmentUpload={attachmentUpload} />
                      {style === "selected" ? <SelectionMask /> : null}
                      {isMenuOpen || isHovered || style === "active" ? (
                        <ActionsButton styled={{ outside: size !== undefined && size.width < minSizeForActionsOutside }}>
                          <AttachmentActions
                            attachment={props.attachment}
                            attachmentService={props.attachmentService}
                            editable={props.editable}
                            menuAlign={size !== undefined && size.width < minSizeForMeatballAlignRight ? "left" : "right"}
                            hasPreview
                            onHidePreview={props.onHidePreview}
                            onMenuToggle={setMenuOpenTo}
                            onRemove={props.onRemove}
                          />
                        </ActionsButton>
                      ) : null}
                    </RelativeContainer>
                  </HoverContainer>
                )}
              </FlipFlop>
            )}
          </FlipFlop>
        )}
      </AttachmentUploadSubscribe>
    );
  }
}

export class AttachmentImage extends React.PureComponent<Props> {
  public render() {
    const { props } = this;
    const {
      attachment: { naturalSize }
    } = props;

    return (
      <AttachmentPreviewSubscribe {...props}>
        {(preview): React.ReactNode => {
          switch (preview.type) {
            case "image":
              // The awkwardness here of rendering a <ResponsiveImage /> when
              // the size is known is to avoid layout jank while the image is
              // loading.
              //
              // https://stackoverflow.com/questions/45869454/responsive-img-without-reflow
              // describes the problem, but the solution offered doesn't work
              // for images that should be smaller than 100%.
              //
              // When `naturalSize` is known, the strategy used here is to use
              // <ResponsiveImage /> to take up space, rather than the
              // <picture>. The picture is absolutely positioned and renders
              // over the space preserved by <ResponsiveImage />.
              return (
                <ImageContainer {...props} size={naturalSize}>
                  {naturalSize !== undefined ? <ResponsiveImage {...naturalSize} /> : null}
                  <picture
                    style={naturalSize !== undefined ? { display: "block", position: "absolute", top: 0, left: 0 } : undefined}
                  >
                    {renderSources(preview.sources)}
                    <ResponsiveImage src={preview.url} {...naturalSize} />
                  </picture>
                </ImageContainer>
              );
            case "pending":
            case "unavailable":
              return (
                <ImageContainer {...props} progressStyle="dark" size={naturalSize}>
                  <ResponsiveImage {...(naturalSize !== undefined ? naturalSize : unavailableUnknownSizeFallback)} />
                  <OpaqueMask>
                    {preview.type === "unavailable" ? (
                      "Preview unavailable"
                    ) : naturalSize !== undefined &&
                    naturalSize.width >= minSizeForIcon &&
                    naturalSize.height >= minSizeForIcon ? (
                      <IconLoading />
                    ) : null}
                  </OpaqueMask>
                </ImageContainer>
              );
            case "audio":
              return null;
          }
        }}
      </AttachmentPreviewSubscribe>
    );
  }
}

function renderSources(sources: AttachmentPreviewImage["sources"]): React.ReactNode {
  if (sources === undefined) {
    return null;
  }

  const grouped = new Map<string, Array<{ url: string; width: number }>>();
  for (const source of sources) {
    const lookup = grouped.get(source.type);
    const items = lookup !== undefined ? lookup : [];
    if (lookup === undefined) {
      grouped.set(source.type, items);
    }
    items.push(source);
  }

  return (
    Array.from(grouped.entries())
      // Set image/webp to be first. It's a most sophisticated format and
      // supports animation, which is good if the user uploaded a .GIF as it
      // provides an animated preview.
      .sort(([aType], [bType]) => typeRank(aType) - typeRank(bType))
      .map(([type, sources]) => (
        <source
          key={type}
          type={type}
          srcSet={sources.map(s => `${s.url} ${s.width}w`).join(", ")}
          sizes="(min-width: 921px) 920px, 100vw"
        />
      ))
  );
}

/**
 * A function to rank different image types (lower is better).
 * @param type
 */
function typeRank(type: string): number {
  switch (type) {
    case "image/webp":
      return 0;
    default:
      return 1;
  }
}

const HoverContainer = styled("div", {
  lineHeight: 0,
  width: "100%"
});

const RelativeContainer = styled("div", {
  display: "inline-block",
  position: "relative"
});

const InnerContainer = styled("div", ({ style }: { style?: "active" | "selected" }) => ({
  boxShadow: style === "active" ? `0 0 0 2px ${COLORS.blue}` : undefined,
  borderRadius: BORDER_RADIUS,
  display: "inline-block",
  overflow: "hidden",
  position: "relative"
}));

const ActionsButton = styled("div", ({ outside = false }: { outside?: boolean }) => ({
  backgroundColor: outside ? undefined : "rgba(36, 18, 77, 0.2)",
  borderRadius: BORDER_RADIUS,
  color: outside ? COLORS.i60 : COLORS.white,
  position: "absolute",
  right: outside ? -48 : 8,
  top: outside ? 0 : 8
}));

const OpaqueMask = styled("div", {
  alignItems: "center",
  backgroundColor: COLORS.i04,
  borderRadius: BORDER_RADIUS,
  bottom: 0,
  color: COLORS.i60,
  fontSize: "14px",
  fontWeight: 600,
  display: "flex",
  justifyContent: "center",
  left: 0,
  position: "absolute",
  right: 0,
  top: 0
});

const SemiOpaqueMask = styled("div", {
  backgroundColor: "rgba(255, 255, 255, 0.6)",
  bottom: 0,
  left: 0,
  position: "absolute",
  right: 0,
  top: 0
});

const UploadStateMask: React.SFC<{ progressStyle: "light" | "dark"; attachmentUpload: AttachmentUpload }> = ({
  progressStyle,
  attachmentUpload
}) => {
  switch (attachmentUpload.type) {
    case "uploading":
      return (
        <SemiOpaqueMask>
          <UploadProgressBarMask
            completed={attachmentUpload.bytesUploaded / attachmentUpload.bytesTotal}
            style={progressStyle}
          />
        </SemiOpaqueMask>
      );
    case "queued":
      return (
        <SemiOpaqueMask>
          <UploadProgressBarMask completed={0 / 1} style={progressStyle} />
        </SemiOpaqueMask>
      );
    case "failed":
      return <OpaqueMask>Upload failed</OpaqueMask>;
    case "unknown":
    case "complete":
      return null;
  }
};

export function UploadProgressBarMask(props: { completed: number; style: "light" | "dark" }) {
  return (
    <ProgressBarContainer>
      <ProgressBar completed={props.completed} style={props.style} />
    </ProgressBarContainer>
  );
}

const ProgressBarContainer = styled("div", {
  bottom: "8px",
  left: "8px",
  position: "absolute",
  right: "8px",
  top: `calc(100% - 4px - 8px)`
});
