import {
  Component,
  For,
  onCleanup,
  createSignal,
  Show,
  untrack,
  Match,
  Switch as JsSwitch,
  ErrorBoundary,
} from "solid-js";
import { type mastodon } from "masto";
import {
  Button,
  LinearProgress,
} from "@suid/material";
import TootThread from "./TootThread.js";
import { useTimeline } from "../masto/timelines";
import { vibrate } from "../platform/hardware";
import PullDownToRefresh from "./PullDownToRefresh";
import TootComposer from "./TootComposer";

const TimelinePanel: Component<{
  client: mastodon.rest.Client;
  name: "home" | "public" | "trends";
  prefetch?: boolean;
  fullRefetch?: number;

  openFullScreenToot: (
    toot: mastodon.v1.Status,
    srcElement?: HTMLElement,
    reply?: boolean,
  ) => void;
}> = (props) => {
  const [scrollLinked, setScrollLinked] = createSignal<HTMLElement>();
  const [
    timeline,
    snapshot,
    { refetch: refetchTimeline, mutate: mutateTimeline },
  ] = useTimeline(
    () =>
      props.name !== "trends"
        ? props.client.v1.timelines[props.name]
        : props.client.v1.trends.statuses,
    { fullRefresh: props.fullRefetch },
  );
  const [expandedThreadId, setExpandedThreadId] = createSignal<string>();
  const [typing, setTyping] = createSignal(false);

  const tlEndObserver = new IntersectionObserver(() => {
    if (untrack(() => props.prefetch) && !snapshot.loading)
      refetchTimeline({ direction: "old" });
  });

  onCleanup(() => tlEndObserver.disconnect());

  const onBookmark = async (
    index: number,
    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());
    mutateTimeline((o) => {
      o[index] = result;
      return o;
    });
  };

  const onBoost = async (
    index: number,
    client: mastodon.rest.Client,
    status: mastodon.v1.Status,
  ) => {
    const reblogged = status.reblog
      ? status.reblog.reblogged
      : status.reblogged;
    vibrate(50);
    mutateTimeline(index, (x) => {
      if (x.reblog) {
        x.reblog = { ...x.reblog, reblogged: !reblogged };
        return Object.assign({}, x);
      } else {
        return Object.assign({}, x, {
          reblogged: !reblogged,
        });
      }
    });
    const result = reblogged
      ? await client.v1.statuses.$select(status.id).unreblog()
      : (await client.v1.statuses.$select(status.id).reblog()).reblog!;
    mutateTimeline((o) => {
      Object.assign(o[index].reblog ?? o[index], {
        reblogged: result.reblogged,
        reblogsCount: result.reblogsCount,
      });
      return o;
    });
  };

  return (
    <ErrorBoundary
      fallback={(err, reset) => {
        return <p>Oops: {String(err)}</p>;
      }}
    >
      <PullDownToRefresh
        linkedElement={scrollLinked()}
        loading={snapshot.loading}
        onRefresh={() => refetchTimeline({ direction: "new" })}
      />
      <div
        ref={(e) =>
          setTimeout(() => {
            setScrollLinked(e.parentElement!);
          }, 0)
        }
      >
        <Show when={props.name === "home"}>
          <TootComposer
            style={{
              "--scaffold-topbar-height": "0px",
            }}
            isTyping={typing()}
            onTypingChange={setTyping}
            client={props.client}
            onSent={() => refetchTimeline({ direction: "new" })}
          />
        </Show>
        <For each={timeline}>
          {(item, index) => {
            let element: HTMLElement | undefined;
            return (
              <TootThread
                ref={element}
                status={item}
                onBoost={(...args) => onBoost(index(), ...args)}
                onBookmark={(...args) => onBookmark(index(), ...args)}
                onReply={(client, status) =>
                  props.openFullScreenToot(status, element, true)
                }
                client={props.client}
                expanded={item.id === expandedThreadId() ? 1 : 0}
                onExpandChange={(x) => {
                  setTyping(false)
                  if (item.id !== expandedThreadId()) {
                    setExpandedThreadId((x) => (x ? undefined : item.id));
                  } else if (x === 2) {
                    props.openFullScreenToot(item, element);
                  }
                }}
              />
            );
          }}
        </For>
      </div>

      <div ref={(e) => tlEndObserver.observe(e)}></div>
      <Show when={snapshot.loading}>
        <div
          class="loading-line"
          style={{
            width: "100%",
          }}
        >
          <LinearProgress />
        </div>
      </Show>
      <div
        style={{
          display: "flex",
          padding: "20px 0 calc(20px + var(--safe-area-inset-bottom, 0px))",
          "align-items": "center",
          "justify-content": "center",
        }}
      >
        <JsSwitch>
          <Match when={snapshot.error}>
            <Button
              variant="contained"
              onClick={[refetchTimeline, "old"]}
              disabled={snapshot.loading}
            >
              Retry
            </Button>
          </Match>
          <Match when={typeof props.fullRefetch === "undefined"}>
            <Button
              variant="contained"
              onClick={[refetchTimeline, "old"]}
              disabled={snapshot.loading}
            >
              Load More
            </Button>
          </Match>
        </JsSwitch>
      </div>
    </ErrorBoundary>
  );
};

export default TimelinePanel