import {
  Component,
  For,
  createSignal,
  ErrorBoundary,
  type Ref,
  createSelector,
  Index,
  createMemo,
} from "solid-js";
import { type mastodon } from "masto";
import { vibrate } from "../platform/hardware";
import { useDefaultSession } from "../masto/clients";
import { useHeroSource } from "../platform/anim";
import { HERO as BOTTOM_SHEET_HERO } from "../material/BottomSheet";
import { setCache as setTootBottomSheetCache } from "./TootBottomSheet";
import { useNavigate } from "@solidjs/router";
import RegularToot, {
  findElementActionable,
  findRootToot,
} from "./RegularToot";
import cardStyle from "../material/cards.module.css";

function positionTootInThread(index: number, threadLength: number) {
  if (index === 0) {
    return "top";
  } else if (index === threadLength - 1) {
    return "bottom";
  }
  return "middle";
}

const TootList: Component<{
  ref?: Ref<HTMLDivElement>;
  id?: string;
  threads: readonly string[];
  onUnknownThread: (id: string) => { value: mastodon.v1.Status }[] | undefined;
  onChangeToot: (id: string, value: mastodon.v1.Status) => void;
}> = (props) => {
  const session = useDefaultSession();
  const heroSrc = useHeroSource();
  const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
  const navigate = useNavigate();

  const onBookmark = async (status: mastodon.v1.Status) => {
    const client = session()?.client;
    if (!client) return;

    const result = await (status.bookmarked
      ? client.v1.statuses.$select(status.id).unbookmark()
      : client.v1.statuses.$select(status.id).bookmark());
    props.onChangeToot(result.id, result);
  };

  const toggleBoost = async (status: mastodon.v1.Status) => {
    const client = session()?.client;
    if (!client) return;

    vibrate(50);
    const rootStatus = status.reblog ? status.reblog : status;
    const reblogged = rootStatus.reblogged;
    if (status.reblog) {
      props.onChangeToot(status.id, {
        ...status,
        reblog: { ...status.reblog, reblogged: !reblogged },
      });
    } else {
      props.onChangeToot(status.id, {
        ...status,
        reblogged: !reblogged,
      });
    }

    const result = reblogged
      ? await client.v1.statuses.$select(status.id).unreblog()
      : (await client.v1.statuses.$select(status.id).reblog()).reblog!;

    if (status.reblog) {
      props.onChangeToot(status.id, {
        ...status,
        reblog: result,
      });
    } else {
      props.onChangeToot(status.id, result);
    }
  };

  const toggleFavourite = async (status: mastodon.v1.Status) => {
    const client = session()?.client;
    if (!client) return;
    const ovalue = status.favourited;
    const result = ovalue
      ? await client.v1.statuses.$select(status.id).unfavourite()
      : await client.v1.statuses.$select(status.id).favourite();
    props.onChangeToot(status.id, result);
  };

  const openFullScreenToot = (
    toot: mastodon.v1.Status,
    srcElement?: HTMLElement,
    reply?: boolean,
  ) => {
    const p = session()?.account;
    if (!p) return;
    const inf = p.inf;
    if (!inf) {
      console.warn("no account info?");
      return;
    }
    if (heroSrc) {
      heroSrc[1]((x) => ({ ...x, [BOTTOM_SHEET_HERO]: srcElement }));
    }

    const acct = `${inf.username}@${p.site}`;
    setTootBottomSheetCache(acct, toot);
    navigate(`/${encodeURIComponent(acct)}/toot/${toot.id}`, {
      state: reply
        ? {
            tootReply: true,
          }
        : undefined,
    });
  };

  const onItemClick = (
    status: mastodon.v1.Status,
    event: MouseEvent & { target: EventTarget; currentTarget: HTMLElement },
  ) => {
    if (!(event.target instanceof HTMLElement)) {
      throw new Error("target is not an element");
    }
    const actionableElement = findElementActionable(
      event.target,
      event.currentTarget,
    );

    if (actionableElement && checkIsExpended(status)) {
      if (actionableElement.dataset.action === "acct") {
        event.stopPropagation();

        const target = actionableElement as HTMLAnchorElement;

        const acct = encodeURIComponent(
          target.dataset.client || `@${new URL(target.href).origin}`,
        );

        navigate(`/${acct}/profile/${target.dataset.acctId}`);

        return;
      } else {
        console.warn("unknown action", actionableElement.dataset.rel);
      }
    } else if (
      event.target.parentElement &&
      event.target.parentElement.tagName === "A"
    ) {
      return;
    }

    // else if (!actionableElement || !checkIsExpended(status) || <rel is not one of known action>)
    if (status.id !== expandedThreadId()) {
      setExpandedThreadId((x) => (x ? undefined : status.id));
    } else {
      openFullScreenToot(status, event.currentTarget as HTMLElement);
    }
  };

  const checkIsExpendedId = createSelector(expandedThreadId);

  const checkIsExpended = (status: mastodon.v1.Status) =>
    checkIsExpendedId(status.id);

  const getPath = (itemId: string) => {
    return props
      .onUnknownThread(itemId)!
      .reverse()
      .map((x) => x.value);
  };

  return (
    <ErrorBoundary
      fallback={(err, reset) => {
        return <p>Oops: {String(err)}</p>;
      }}
    >
      <div ref={props.ref} id={props.id} class="toot-list">
        <For each={props.threads}>
          {(itemId) => {
            const toots = createMemo(() => getPath(itemId));
            return (
              <Index each={toots()}>
                {(status, index) => (
                  <RegularToot
                    data-status-id={status().id}
                    data-thread-sort={index}
                    status={status()}
                    thread={
                      toots().length > 1
                        ? positionTootInThread(index, toots().length)
                        : undefined
                    }
                    class={cardStyle.card}
                    evaluated={checkIsExpended(status())}
                    actionable={checkIsExpended(status())}
                    onBookmark={onBookmark}
                    onRetoot={toggleBoost}
                    onFavourite={toggleFavourite}
                    onReply={(status, event) => {
                      const element = findRootToot(event.currentTarget);
                      openFullScreenToot(status, element, true);
                    }}
                    onClick={[onItemClick, status()]}
                  />
                )}
              </Index>
            );
          }}
        </For>
      </div>
    </ErrorBoundary>
  );
};

export default TootList;
