import type { mastodon } from "masto";
import {
  splitProps,
  type Component,
  type JSX,
  Show,
  createRenderEffect,
  createEffect,
  createMemo,
} from "solid-js";
import tootStyle from "./toot.module.css";
import { formatRelative } from "date-fns";
import Img from "../material/Img.js";
import { Body1, Body2, Title } from "../material/typography.js";
import { css } from "solid-styled";
import {
  BookmarkAddOutlined,
  Repeat,
  ReplyAll,
  Star,
  StarOutline,
  Bookmark,
  Reply,
  Share,
} from "@suid/icons-material";
import { useTimeSource } from "../platform/timesrc.js";
import { resolveCustomEmoji } from "../masto/toot.js";
import { Divider } from "@suid/material";
import cardStyle from "../material/cards.module.css";
import Button from "../material/Button.js";
import MediaAttachmentGrid from "./MediaAttachmentGrid.js";
import { FastAverageColor } from "fast-average-color";
import Color from "colorjs.io";
import { useDateFnLocale } from "../platform/i18n";
import { canShare, share } from "../platform/share";
import { makeAcctText, useDefaultSession } from "../masto/clients";
import { useNavigate } from "@solidjs/router";

function preventDefault(event: Event) {
  event.preventDefault();
}

type TootContentViewProps = {
  source?: string;
  emojis?: mastodon.v1.CustomEmoji[];
  mentions: mastodon.v1.StatusMention[];
} & JSX.HTMLAttributes<HTMLDivElement>;

const TootContentView: Component<TootContentViewProps> = (props) => {
  const session = useDefaultSession();
  const [managed, rest] = splitProps(props, ["source", "emojis", "mentions"]);

  const clientFinder = createMemo(() =>
    session() ? makeAcctText(session()!) : undefined,
  );

  return (
    <div
      ref={(ref) => {
        createRenderEffect(() => {
          ref.innerHTML = managed.source
            ? managed.emojis
              ? resolveCustomEmoji(managed.source, managed.emojis)
              : managed.source
            : "";
        });

        createRenderEffect(() => {
          const finder = clientFinder();
          for (const mention of props.mentions) {
            const elements = ref.querySelectorAll<HTMLAnchorElement>(
              `a[href='${mention.url}']`,
            );
            for (const e of elements) {
              e.onclick = preventDefault;
              e.dataset.rel = "acct";
              e.dataset.client = finder;
              e.dataset.acctId = mention.id.toString();
            }
          }
        });
      }}
      {...rest}
    ></div>
  );
};

const RetootIcon: Component<JSX.HTMLElementTags["i"]> = (props) => {
  const [managed, rest] = splitProps(props, ["class"]);
  css`
    .retoot-icon {
      padding: 0;
      display: inline-block;
      border-radius: 2px;

      > :global(svg) {
        color: green;
        font-size: 1rem;
        vertical-align: middle;
      }
    }
  `;
  return (
    <i class={["retoot-icon", managed.class].join(" ")} {...rest}>
      <Repeat />
    </i>
  );
};

const ReplyIcon: Component<JSX.HTMLElementTags["i"]> = (props) => {
  const [managed, rest] = splitProps(props, ["class"]);
  css`
    .retoot-icon {
      padding: 0;
      display: inline-block;
      border-radius: 2px;

      > :global(svg) {
        color: var(--tutu-color-primary);
        font-size: 1rem;
        vertical-align: middle;
      }
    }
  `;
  return (
    <i class={["retoot-icon", managed.class].join(" ")} {...rest}>
      <Reply />
    </i>
  );
};

type TootActionGroupProps<T extends mastodon.v1.Status> = {
  onRetoot?: (value: T) => void;
  onFavourite?: (value: T) => void;
  onBookmark?: (value: T) => void;
  onReply?: (
    value: T,
    event: MouseEvent & { currentTarget: HTMLButtonElement },
  ) => void;
};

type TootCardProps = {
  status: mastodon.v1.Status;
  actionable?: boolean;
  evaluated?: boolean;
  thread?: "top" | "bottom" | "middle";
} & TootActionGroupProps<mastodon.v1.Status> &
  JSX.HTMLElementTags["article"];

function isolatedCallback(e: MouseEvent) {
  e.stopPropagation();
}

export function findRootToot(element: HTMLElement) {
  let current: HTMLElement | null = element;
  while (current && !current.classList.contains(tootStyle.toot)) {
    current = current.parentElement;
  }
  if (!current) {
    throw Error(
      `the element must be placed under a element with ${tootStyle.toot}`,
    );
  }
  return current;
}

function TootActionGroup<T extends mastodon.v1.Status>(
  props: TootActionGroupProps<T> & { value: T },
) {
  let actGrpElement: HTMLDivElement;
  const toot = () => props.value;
  return (
    <div
      ref={actGrpElement!}
      class={tootStyle.tootBottomActionGrp}
      onClick={isolatedCallback}
    >
      <Show when={props.onReply}>
        <Button
          class={tootStyle.tootActionWithCount}
          onClick={[props.onReply!, props.value]}
        >
          <ReplyAll />
          <span>{toot().repliesCount}</span>
        </Button>
      </Show>

      <Button
        class={tootStyle.tootActionWithCount}
        style={{
          color: toot().reblogged ? "var(--tutu-color-primary)" : undefined,
        }}
        onClick={() => props.onRetoot?.(toot())}
      >
        <Repeat />
        <span>{toot().reblogsCount}</span>
      </Button>
      <Button
        class={tootStyle.tootActionWithCount}
        style={{
          color: toot().favourited ? "var(--tutu-color-primary)" : undefined,
        }}
        onClick={() => props.onFavourite?.(toot())}
      >
        {toot().favourited ? <Star /> : <StarOutline />}
        <span>{toot().favouritesCount}</span>
      </Button>
      <Button
        class={tootStyle.tootAction}
        style={{
          color: toot().bookmarked ? "var(--tutu-color-primary)" : undefined,
        }}
        onClick={() => props.onBookmark?.(toot())}
      >
        {toot().bookmarked ? <Bookmark /> : <BookmarkAddOutlined />}
      </Button>
      <Show when={canShare({ url: toot().url ?? undefined })}>
        <Button
          class={tootStyle.tootAction}
          aria-label="Share"
          onClick={async () => {
            await share({
              url: toot().url ?? undefined,
            });
          }}
        >
          <Share />
        </Button>
      </Show>
    </div>
  );
}

function TootAuthorGroup(
  props: {
    status: mastodon.v1.Status;
    now: Date;
  } & JSX.HTMLElementTags["div"],
) {
  const [managed, rest] = splitProps(props, ["status", "now"]);
  const toot = () => managed.status;
  const dateFnLocale = useDateFnLocale();

  return (
    <div class={tootStyle.tootAuthorGrp} {...rest}>
      <Img src={toot().account.avatar} class={tootStyle.tootAvatar} />
      <div class={tootStyle.tootAuthorNameGrp}>
        <Body2
          class={tootStyle.tootAuthorNamePrimary}
          ref={(e: { innerHTML: string }) => {
            createRenderEffect(() => {
              e.innerHTML = resolveCustomEmoji(
                toot().account.displayName,
                toot().account.emojis,
              );
            });
          }}
        />
        <time datetime={toot().createdAt}>
          {formatRelative(toot().createdAt, managed.now, {
            locale: dateFnLocale(),
          })}
        </time>
        <span>
          @{toot().account.username}@{new URL(toot().account.url).hostname}
        </span>
      </div>
    </div>
  );
}

export function TootPreviewCard(props: {
  src: mastodon.v1.PreviewCard;
  alwaysCompact?: boolean;
}) {
  let root: HTMLAnchorElement;

  createEffect(() => {
    if (props.alwaysCompact) {
      root.classList.add(tootStyle.compact);
      return;
    }
    if (!props.src.width) return;
    const width = root.getBoundingClientRect().width;
    if (width > props.src.width) {
      root.classList.add(tootStyle.compact);
    } else {
      root.classList.remove(tootStyle.compact);
    }
  });

  const onImgLoad = (event: Event & { currentTarget: HTMLImageElement }) => {
    // TODO: better extraction algorithm
    // I'd like to use a pattern panel and match one in the panel from the extracted color
    const fac = new FastAverageColor();
    const result = fac.getColor(event.currentTarget);
    if (result.error) {
      console.error(result.error);
      fac.destroy();
      return;
    }
    root.style.setProperty("--tutu-color-surface", result.hex);
    const focusSurface = result.isDark
      ? new Color(result.hex).darken(0.2)
      : new Color(result.hex).lighten(0.2);
    root.style.setProperty("--tutu-color-surface-d", focusSurface.toString());
    const textColor = result.isDark ? "white" : "black";
    const secondaryTextColor = new Color(textColor);
    secondaryTextColor.alpha = 0.75;
    root.style.setProperty("--tutu-color-on-surface", textColor);
    root.style.setProperty(
      "--tutu-color-secondary-text-on-surface",
      secondaryTextColor.toString(),
    );
    fac.destroy();
  };

  return (
    <a
      ref={root!}
      class={tootStyle.previewCard}
      href={props.src.url}
      target="_blank"
      referrerPolicy="unsafe-url"
    >
      <Show when={props.src.image}>
        <img
          crossOrigin="anonymous"
          src={props.src.image!}
          onLoad={onImgLoad}
          width={props.src.width || undefined}
          height={props.src.height || undefined}
          loading="lazy"
        />
      </Show>
      <Title component="h1">{props.src.title}</Title>
      <Body1 component="p">{props.src.description}</Body1>
    </a>
  );
}

/**
 * Component for a toot.
 *
 * If the session involved is not the first session, you must wrap
 * this component under a `<DefaultSessionProvier />` with correct
 * session.
 */
const RegularToot: Component<TootCardProps> = (props) => {
  let rootRef: HTMLElement;
  const [managed, managedActionGroup, rest] = splitProps(
    props,
    ["status", "lang", "class", "actionable", "evaluated", "thread"],
    ["onRetoot", "onFavourite", "onBookmark", "onReply"],
  );
  const now = useTimeSource();
  const status = () => managed.status;
  const toot = () => status().reblog ?? status();
  const session = useDefaultSession();
  const navigate = useNavigate();

  const openProfile = (event: MouseEvent) => {
    if (!managed.evaluated) return;
    event.stopPropagation();

    const s = session();
    if (!s) {
      console.warn("No session is provided");
      return;
    }

    const acct = makeAcctText(s);

    navigate(
      `/${encodeURIComponent(acct)}/profile/${managed.status.account.id}`,
    );
  };

  css`
    .reply-sep {
      margin-left: calc(var(--toot-avatar-size) + var(--card-pad) + 8px);
      margin-block: 8px;
    }

    .thread-top,
    .thread-mid,
    .thread-btm {
      position: relative;

      &::before {
        content: "";
        position: absolute;
        left: 36px;
        background-color: var(--tutu-color-secondary);
        width: 2px;
        display: block;
      }
    }

    .thread-mid {
      &::before {
        top: 0;
        bottom: 0;
      }
    }

    .thread-top {
      &::before {
        top: 16px;
        bottom: 0;
      }
    }

    .thread-btm {
      &::before {
        top: 0;
        height: 16px;
      }
    }
  `;

  return (
    <>
      <section
        classList={{
          [tootStyle.toot]: true,
          [tootStyle.expanded]: managed.evaluated,
          "thread-top": managed.thread === "top",
          "thread-mid": managed.thread === "middle",
          "thread-btm": managed.thread === "bottom",
          [managed.class || ""]: true,
        }}
        ref={rootRef!}
        lang={toot().language || managed.lang}
        {...rest}
      >
        <Show when={!!status().reblog}>
          <div class={tootStyle.tootRetootGrp}>
            <RetootIcon />
            <span>
              <Body2
                ref={(e: { innerHTML: string }) => {
                  createRenderEffect(() => {
                    e.innerHTML = resolveCustomEmoji(
                      status().account.displayName,
                      toot().emojis,
                    );
                  });
                }}
              ></Body2>{" "}
              boosted
            </span>
          </div>
        </Show>
        <TootAuthorGroup
          status={toot()}
          now={now()}
          data-rel="acct"
          data-client={session() ? makeAcctText(session()!) : undefined}
          data-acct-id={toot().account.id}
        />
        <TootContentView
          source={toot().content}
          emojis={toot().emojis}
          mentions={toot().mentions}
          class={tootStyle.tootContent}
        />
        <Show when={toot().card}>
          <TootPreviewCard src={toot().card!} />
        </Show>
        <Show when={toot().mediaAttachments.length > 0}>
          <MediaAttachmentGrid attachments={toot().mediaAttachments} />
        </Show>
        <Show when={managed.actionable}>
          <Divider
            class={cardStyle.cardNoPad}
            style={{ "margin-top": "8px" }}
          />
          <TootActionGroup value={toot()} {...managedActionGroup} />
        </Show>
      </section>
    </>
  );
};

export default RegularToot;