import {
  Component,
  For,
  createSignal,
  ErrorBoundary,
  type Ref,
  createSelector,
} from "solid-js";
import { type mastodon } from "masto";
import { vibrate } from "../platform/hardware";
import Thread from "./Thread.jsx";
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 { findElementActionable } from "./RegularToot";

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 (
    client: mastodon.rest.Client,
    status: mastodon.v1.Status,
  ) => {
    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 (
    client: mastodon.rest.Client,
    status: mastodon.v1.Status,
  ) => {
    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: HTMLElement; currentTarget: HTMLElement },
  ) => {
    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 onReply = (
    { status }: { status: mastodon.v1.Status },
    element: HTMLElement,
  ) => {
    openFullScreenToot(status, element, true);
  };

  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) => {
            return (
              <Thread
                toots={getPath(itemId)}
                onBoost={toggleBoost}
                onBookmark={onBookmark}
                onReply={onReply}
                onFavourite={toggleFavourite}
                client={session()?.client!}
                isExpended={checkIsExpended}
                onItemClick={onItemClick}
              />
            );
          }}
        </For>
      </div>
    </ErrorBoundary>
  );
};

export default TootList;