import type { mastodon } from "masto";
import {
  splitProps,
  type Component,
  type JSX,
  Show,
  createRenderEffect,
  createSignal,
  type Setter,
  createContext,
  useContext,
} from "solid-js";
import tootStyle from "./toot.module.css";
import { formatRelative, parseISO } from "date-fns";
import Img from "~material/Img.js";
import { Body2 } from "~material/typography.js";
import { css } from "solid-styled";
import {
  BookmarkAddOutlined,
  Repeat,
  ReplyAll,
  Star,
  StarOutline,
  Bookmark,
  Share,
  SmartToySharp,
  Lock,
} 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 "./toots/MediaAttachmentGrid.jsx";
import { useDateFnLocale } from "~platform/i18n";
import { canShare, share } from "~platform/share";
import { makeAcctText, useDefaultSession } from "../masto/clients";
import TootContent from "./toots/TootContent";
import BoostIcon from "./toots/BoostIcon";
import PreviewCard from "./toots/PreviewCard";
import TootPoll from "./toots/TootPoll";

export type TootEnv = {
  boost: (value: mastodon.v1.Status) => void;
  favourite: (value: mastodon.v1.Status) => void;
  bookmark: (value: mastodon.v1.Status) => void;
  reply?: (
    value: mastodon.v1.Status,
    event: MouseEvent & { currentTarget: HTMLButtonElement },
  ) => void;
  vote: (
    status: mastodon.v1.Status,
    votes: readonly number[],
  ) => void | Promise<void>;
};

const TootEnvContext = /* @__PURE__ */ createContext<TootEnv>();

export const TootEnvProvider = TootEnvContext.Provider;

export function useTootEnv() {
  const env = useContext(TootEnvContext);
  if (!env) {
    throw new TypeError("environment not found, use TootEnvProvider to provide")
  }
  return env
}

type RegularTootProps = {
  status: mastodon.v1.Status;
  actionable?: boolean;
  evaluated?: boolean;
  thread?: "top" | "bottom" | "middle";
} &
  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: { value: T },
) {
  const {reply, boost, favourite, bookmark} = useTootEnv()
  let actGrpElement: HTMLDivElement;
  const toot = () => props.value;
  return (
    <div
      ref={actGrpElement!}
      class={tootStyle.tootBottomActionGrp}
      onClick={isolatedCallback}
    >
      <Show when={reply}>
        <Button
          class={tootStyle.tootActionWithCount}
          onClick={[reply!, props.value]}
        >
          <ReplyAll />
          <span>{toot().repliesCount}</span>
        </Button>
      </Show>

      <Button
        class={tootStyle.tootActionWithCount}
        style={{
          color: toot().reblogged ? "var(--tutu-color-primary)" : undefined,
        }}
        onClick={[boost, props.value]}
      >
        <Repeat />
        <span>{toot().reblogsCount}</span>
      </Button>
      <Button
        class={tootStyle.tootActionWithCount}
        style={{
          color: toot().favourited ? "var(--tutu-color-primary)" : undefined,
        }}
        onClick={[favourite, props.value]}
      >
        {toot().favourited ? <Star /> : <StarOutline />}
        <span>{toot().favouritesCount}</span>
      </Button>
      <Button
        class={tootStyle.tootAction}
        style={{
          color: toot().bookmarked ? "var(--tutu-color-primary)" : undefined,
        }}
        onClick={[bookmark, props.value]}
      >
        {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}>
        <div class={tootStyle.tootAuthorNamePrimary}>
          <Show when={toot().account.bot}>
            <SmartToySharp class="acct-mark" aria-label="Bot" />
          </Show>
          <Show when={toot().account.locked}>
            <Lock class="acct-mark" aria-label="Locked" />
          </Show>
          <Body2
            component="span"
            ref={(e: { innerHTML: string }) => {
              createRenderEffect(() => {
                e.innerHTML = resolveCustomEmoji(
                  toot().account.displayName,
                  toot().account.emojis,
                );
              });
            }}
          />
        </div>
        <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>
  );
}

/**
 * find bottom-to-top the element with `data-action`.
 */
export function findElementActionable(
  element: HTMLElement,
  top: HTMLElement,
): HTMLElement | undefined {
  let current = element;
  while (!current.dataset.action) {
    if (!current.parentElement || current.parentElement === top) {
      return undefined;
    }
    current = current.parentElement;
  }
  return current;
}

function onToggleReveal(setValue: Setter<boolean>, event: Event) {
  event.stopPropagation();
  setValue((x) => !x);
}

/**
 * Component for a toot.
 *
 * If the session involved is not the first session, you must wrap
 * this component under a `<DefaultSessionProvier />` with correct
 * session.
 *
 * **Handling Clicks**
 * There are multiple actions supported in the component. Some handlers
 * are passed in, some should be handled as the click event.
 *
 * For those handler directly passed in, see the props starts with "on".
 * We are moving to the new method below.
 *
 * The following actions are handled by the click event:
 * - `[data-action="acct"]`: open the profile page of a account
 *   - `[data-acct-id]` is the account id for the client
 *   - `[data-client]` is the client perferred
 *   - `[href]` is the url of the account
 *
 * Handling the click event for this component, you should use
 * {@link findElementActionable} to find out if the click event has
 * additional intent. If the event's target is any from
 * the subtree of any "actionable" element, the function returns the element.
 *
 * You can extract the intent from the attributes of the "actionable" element.
 * The action type is the dataset's `action`.
 */
const RegularToot: Component<RegularTootProps> = (props) => {
  let rootRef: HTMLElement;
  const {vote} = useTootEnv()
  const [managed, rest] = splitProps(
    props,
    ["status", "lang", "class", "actionable", "evaluated", "thread"],
  );
  const now = useTimeSource();
  const status = () => managed.status;
  const toot = () => status().reblog ?? status();
  const session = useDefaultSession();
  const [reveal, setReveal] = createSignal(false);

  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 (
    <>
      <article
        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}>
            <BoostIcon />
            <Body2
              ref={(e: { innerHTML: string }) => {
                createRenderEffect(() => {
                  e.innerHTML = resolveCustomEmoji(
                    status().account.displayName,
                    toot().emojis,
                  );
                });
              }}
            ></Body2>
            <span>boosts</span>
          </div>
        </Show>
        <TootAuthorGroup
          status={toot()}
          now={now()}
          data-action="acct"
          data-client={session() ? makeAcctText(session()!) : undefined}
          data-acct-id={toot().account.id}
        />
        <TootContent
          source={toot().content}
          emojis={toot().emojis}
          mentions={toot().mentions}
          class={cardStyle.cardNoPad}
          sensitive={toot().sensitive}
          spoilerText={toot().spoilerText}
          reveal={reveal()}
          onToggleReveal={[onToggleReveal, setReveal]}
        />
        <Show
          when={
            toot().card && (!toot().sensitive || (toot().sensitive && reveal()))
          }
        >
          <PreviewCard src={toot().card!} />
        </Show>
        <Show when={toot().mediaAttachments.length > 0}>
          <MediaAttachmentGrid
            attachments={toot().mediaAttachments}
            sensitive={toot().sensitive}
          />
        </Show>
        <Show when={toot().poll}>
          <TootPoll
            value={toot().poll!}
            status={toot()}
          />
        </Show>
        <Show when={managed.actionable}>
          <Divider
            class={cardStyle.cardNoPad}
            style={{ "margin-top": "8px" }}
          />
          <TootActionGroup value={toot()} />
        </Show>
      </article>
    </>
  );
};

export default RegularToot;
