import type { mastodon } from "masto";
import {
  type Component,
  Index,
  Match,
  Switch,
  createMemo,
  createRenderEffect,
  createSignal,
  onCleanup,
  untrack,
} from "solid-js";
import MediaViewer from "../MediaViewer";
import { render } from "solid-js/web";
import {
  createElementSize,
  useWindowSize,
} from "@solid-primitives/resize-observer";
import { useStore } from "@nanostores/solid";
import { $settings } from "../../settings/stores";
import { averageColorHex } from "~platform/blurhash";
import "./MediaAttachmentGrid.css";
import cardStyle from "~material/cards.module.css";
import { Preview } from "@suid/icons-material";
import { IconButton } from "@suid/material";
import Masonry from "~platform/Masonry";

type ElementSize = { width: number; height: number };

function constraintedSize(
  { width: owidth, height: oheight }: Readonly<ElementSize>, // originalSize
  { width: mwidth, height: mheight }: Readonly<Partial<ElementSize>>, // modifier
  { width: maxWidth, height: maxHeight }: Readonly<ElementSize>, // maxSize
) {
  const ySize = owidth + (mwidth ?? 0);
  const yScale = ySize > maxWidth ? ySize / maxWidth : 1;
  const xSize = oheight + (mheight ?? 0);
  const xScale = xSize > maxHeight ? xSize / maxHeight : 1;

  const maxScale = Math.max(yScale, xScale);
  const scaledWidth = owidth / maxScale;
  const scaledHeight = oheight / maxScale;

  return {
    width: scaledWidth,
    height: scaledHeight,
  };
}

function isolateCallback(event: Event) {
  if (event.target !== event.currentTarget) {
    event.stopPropagation();
  }
}

const MediaAttachmentGrid: Component<{
  attachments: mastodon.v1.MediaAttachment[];
  sensitive?: boolean;
}> = (props) => {
  const [rootRef, setRootRef] = createSignal<HTMLElement>();
  const [viewerIndex, setViewerIndex] = createSignal<number>();
  const viewerOpened = () => typeof viewerIndex() !== "undefined";
  const settings = useStore($settings);
  const windowSize = useWindowSize();
  const [reveal, setReveal] = createSignal([] as number[]);

  createRenderEffect(() => {
    const vidx = viewerIndex();
    if (typeof vidx === "undefined") return;
    const container = document.createElement("div");
    container.setAttribute("role", "presentation");
    document.body.appendChild(container);
    const dispose = render(() => {
      onCleanup(() => {
        document.body.removeChild(container);
      });

      return (
        <MediaViewer
          show={viewerOpened()}
          index={viewerIndex() || 0}
          onIndexUpdated={setViewerIndex}
          media={props.attachments}
          onClose={() => setViewerIndex()}
        />
      );
    }, container);

    onCleanup(dispose);
  });

  const openViewerFor = (index: number) => {
    setViewerIndex(index);
  };

  const columnCount = () => {
    if (props.attachments.length === 1) {
      return 1;
    } else if (props.attachments.length % 2 === 0) {
      return 2;
    } else {
      return 3;
    }
  };

  const rawElementSize = createElementSize(rootRef);

  const elementWidth = () => rawElementSize.width;

  const itemMaxSize = createMemo(() => {
    const ewidth = elementWidth();
    const width = ewidth
      ? (ewidth - (columnCount() - 1) * 4) / columnCount()
      : 1;

    return {
      height: windowSize.height * 0.35,
      width,
    };
  });

  const itemStyle = (item: mastodon.v1.MediaAttachment) => {
    const { width, height } = constraintedSize(
      item.meta?.small || { width: 1, height: 1 },
      { width: 2, height: 2 },
      itemMaxSize(),
    );

    const accentColor =
      item.meta?.colors?.accent ??
      (item.blurhash ? averageColorHex(item.blurhash) : undefined);

    return Object.assign(
      {
        width: `${width}px`,
        height: `${height}px`,
        "contain-intrinsic-size": `${width}px ${height}px`,
      },
      accentColor ? { "--media-color-accent": accentColor } : {},
    );
  };

  const isReveal = (idx: number) => {
    return reveal().includes(idx);
  };

  const addReveal = (idx: number) => {
    if (!untrack(() => isReveal(idx))) {
      setReveal((x) => [...x, idx]);
    }
  };
  return (
    <Masonry
      component="section"
      ref={setRootRef}
      class={`MediaAttachmentGrid ${cardStyle.cardNoPad}`}
      classList={{
        sensitive: props.sensitive,
      }}
      onClick={isolateCallback}
    >
      <Index each={props.attachments}>
        {(item, index) => {
          const itemType = () => item().type;

          const style = createMemo(() => itemStyle(item()));
          return (
            <div style={style()} role="presentation">
              <Switch>
                <Match when={props.sensitive && !isReveal(index)}>
                  <div
                    class="sensitive-placeholder"
                    data-sort={index}
                    data-media-type={item().type}
                  >
                    <IconButton
                      color="inherit"
                      size="large"
                      onClick={[addReveal, index]}
                      aria-label="Reveal this media"
                    >
                      <Preview />
                    </IconButton>
                  </div>
                </Match>
                <Match when={itemType() === "image"}>
                  <img
                    src={item().previewUrl}
                    width={item().meta?.small?.width}
                    height={item().meta?.small?.height}
                    alt={item().description || undefined}
                    onClick={[openViewerFor, index]}
                    loading="lazy"
                    data-sort={index}
                    data-media-type={item().type}
                  ></img>
                </Match>
                <Match when={itemType() === "video"}>
                  <video
                    src={item().url || undefined}
                    autoplay={!props.sensitive && settings().autoPlayVideos}
                    playsinline={settings().autoPlayVideos ? true : undefined}
                    controls
                    poster={item().previewUrl}
                    width={item().meta?.small?.width}
                    height={item().meta?.small?.height}
                    data-sort={index}
                    data-media-type={item().type}
                    preload="metadata"
                  />
                </Match>
                <Match when={itemType() === "gifv"}>
                  <video
                    src={item().url || undefined}
                    autoplay={!props.sensitive && settings().autoPlayGIFs}
                    controls
                    playsinline /* or safari on iOS will play in full-screen */
                    loop
                    poster={item().previewUrl}
                    width={item().meta?.small?.width}
                    height={item().meta?.small?.height}
                    data-sort={index}
                    data-media-type={item().type}
                    preload="metadata"
                  />
                </Match>
                <Match when={itemType() === "audio"}>
                  <audio
                    src={item().url || undefined}
                    controls
                    data-sort={index}
                    data-media-type={item().type}
                  ></audio>
                </Match>
              </Switch>
            </div>
          );
        }}
      </Index>
    </Masonry>
  );
};

export default MediaAttachmentGrid;
