import {
  Component,
  For,
  onCleanup,
  createSignal,
  Show,
  untrack,
  onMount,
  type ParentComponent,
  children,
  Suspense,
  Match,
  Switch as JsSwitch,
  ErrorBoundary,
} from "solid-js";
import { useDocumentTitle } from "../utils";
import { type mastodon } from "masto";
import Scaffold from "../material/Scaffold";
import {
  AppBar,
  Button,
  Fab,
  LinearProgress,
  ListItemSecondaryAction,
  ListItemText,
  MenuItem,
  Switch,
  Toolbar,
} from "@suid/material";
import { css } from "solid-styled";
import { TimeSourceProvider, createTimeSource } from "../platform/timesrc";
import TootThread from "./TootThread.js";
import ProfileMenuButton from "./ProfileMenuButton";
import Tabs from "../material/Tabs";
import Tab from "../material/Tab";
import { Create as CreateTootIcon } from "@suid/icons-material";
import { useTimeline } from "../masto/timelines";
import { makeEventListener } from "@solid-primitives/event-listener";
import BottomSheet, {
  HERO as BOTTOM_SHEET_HERO,
} from "../material/BottomSheet";
import { $settings } from "../settings/stores";
import { useStore } from "@nanostores/solid";
import { vibrate } from "../platform/hardware";
import PullDownToRefresh from "./PullDownToRefresh";
import { HeroSourceProvider, type HeroSource } from "../platform/anim";
import { useNavigate } from "@solidjs/router";
import { useSignedInProfiles } from "../masto/acct";
import { setCache as setTootBottomSheetCache } from "./TootBottomSheet";
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>
  );
};

const Home: ParentComponent = (props) => {
  let panelList: HTMLDivElement;
  useDocumentTitle("Timelines");
  const now = createTimeSource();

  const settings$ = useStore($settings);

  const [profiles] = useSignedInProfiles();
  const profile = () => {
    const all = profiles();
    if (all.length > 0) {
      return all[0].inf;
    }
  };
  const client = () => {
    const all = profiles();
    return all?.[0]?.client;
  };
  const navigate = useNavigate();

  const [heroSrc, setHeroSrc] = createSignal<HeroSource>({});
  const [panelOffset, setPanelOffset] = createSignal(0);
  const prefetching = () => !settings$().prefetchTootsDisabled;
  const [currentFocusOn, setCurrentFocusOn] = createSignal<HTMLElement[]>([]);
  const [focusRange, setFocusRange] = createSignal([0, 0] as readonly [
    number,
    number,
  ]);

  const child = children(() => props.children);

  let scrollEventLockReleased = true;

  const recalculateTabIndicator = () => {
    scrollEventLockReleased = false;
    try {
      const { x: panelX, width: panelWidth } =
        panelList.getBoundingClientRect();
      let minIdx = +Infinity,
        maxIdx = -Infinity;
      const items = panelList.querySelectorAll(".tab-panel");
      const ranges = Array.from(items).map((x) => {
        const rect = x.getBoundingClientRect();
        const inlineStart = rect.x - panelX;
        const inlineEnd = rect.width + inlineStart;
        return [inlineStart, inlineEnd] as const;
      });
      for (let i = 0; i < items.length; i++) {
        const e = items.item(i);
        const [inlineStart, inlineEnd] = ranges[i];
        if (inlineStart >= 0 && inlineEnd <= panelWidth) {
          minIdx = Math.min(minIdx, i);
          maxIdx = Math.max(maxIdx, i);
          e.classList.add("active");
        } else {
          e.classList.remove("active");
        }
      }

      if (isFinite(minIdx) && isFinite(maxIdx)) {
        setFocusRange([minIdx, maxIdx]);
      }
    } finally {
      scrollEventLockReleased = true;
    }
  };

  onMount(() => {
    makeEventListener(panelList, "scroll", () => {
      if (scrollEventLockReleased) {
        requestAnimationFrame(recalculateTabIndicator);
      }
    });
    makeEventListener(window, "resize", () => {
      if (scrollEventLockReleased) {
        requestAnimationFrame(recalculateTabIndicator);
      }
    });
    requestAnimationFrame(recalculateTabIndicator);
  });

  const isTabFocus = (idx: number) => {
    const [start, end] = focusRange();
    if (!isFinite(start) || !isFinite(end)) return false;
    return idx >= start && idx <= end;
  };

  const onTabClick = (idx: number) => {
    const items = panelList.querySelectorAll(".tab-panel");
    if (items.length > idx) {
      items.item(idx).scrollIntoView({ block: "start", behavior: "smooth" });
    }
    if (isTabFocus(idx)) {
      items.item(idx).scrollTo({
        top: 0,
        behavior: "smooth",
      });
    }
  };

  const openFullScreenToot = (
    toot: mastodon.v1.Status,
    srcElement?: HTMLElement,
    reply?: boolean,
  ) => {
    const p = profiles()[0];
    const inf = p.account.inf ?? profile();
    if (!inf) {
      console.warn("no account info?");
      return;
    }
    setHeroSrc((x) => Object.assign({}, x, { [BOTTOM_SHEET_HERO]: srcElement }));
    const acct = `${inf.username}@${p.account.site}`;
    setTootBottomSheetCache(acct, toot);
    navigate(`/${encodeURIComponent(acct)}/${toot.id}`, {
      state: reply
        ? {
            tootReply: true,
          }
        : undefined,
    });
  };

  css`
    .tab-panel {
      overflow: visible auto;
      max-width: 560px;
      height: 100%;
      padding: 0 16px;
      scroll-snap-align: center;
      overscroll-behavior-block: none;

      @media (max-width: 600px) {
        padding: 0;
      }
    }

    .panel-list {
      display: grid;
      grid-auto-columns: 560px;
      grid-auto-flow: column;
      overflow-x: auto;
      scroll-snap-type: x mandatory;
      scroll-snap-stop: always;
      height: calc(100vh - var(--scaffold-topbar-height, 0px));
      height: calc(100dvh - var(--scaffold-topbar-height, 0px));
      padding-left: var(--safe-area-inset-left, 0);
      padding-right: var(--safe-area-inset-right, 0);

      @media (max-width: 600px) {
        grid-auto-columns: 100%;
      }
    }
  `;

  return (
    <>
      <Scaffold
        topbar={
          <AppBar position="static">
            <Toolbar
              variant="dense"
              class="responsive"
              sx={{ paddingTop: "var(--safe-area-inset-top, 0px)" }}
            >
              <Tabs onFocusChanged={setCurrentFocusOn} offset={panelOffset()}>
                <Tab focus={isTabFocus(0)} onClick={[onTabClick, 0]}>
                  Home
                </Tab>
                <Tab focus={isTabFocus(1)} onClick={[onTabClick, 1]}>
                  Trending
                </Tab>
                <Tab focus={isTabFocus(2)} onClick={[onTabClick, 2]}>
                  Public
                </Tab>
              </Tabs>
              <ProfileMenuButton profile={profile()}>
                <MenuItem
                  onClick={(e) =>
                    $settings.setKey(
                      "prefetchTootsDisabled",
                      !$settings.get().prefetchTootsDisabled,
                    )
                  }
                >
                  <ListItemText>Prefetch Toots</ListItemText>
                  <ListItemSecondaryAction>
                    <Switch checked={prefetching()}></Switch>
                  </ListItemSecondaryAction>
                </MenuItem>
              </ProfileMenuButton>
            </Toolbar>
          </AppBar>
        }
      >
        <TimeSourceProvider value={now}>
          <Show when={!!client()}>
            <div class="panel-list" ref={panelList!}>
              <div class="tab-panel">
                <div>
                  <TimelinePanel
                    client={client()}
                    name="home"
                    prefetch={prefetching()}
                    openFullScreenToot={openFullScreenToot}
                  />
                </div>
              </div>
              <div class="tab-panel">
                <div>
                  <TimelinePanel
                    client={client()}
                    name="trends"
                    prefetch={prefetching()}
                    openFullScreenToot={openFullScreenToot}
                    fullRefetch={120}
                  />
                </div>
              </div>
              <div class="tab-panel">
                <div>
                  <TimelinePanel
                    client={client()}
                    name="public"
                    prefetch={prefetching()}
                    openFullScreenToot={openFullScreenToot}
                  />
                </div>
              </div>
              <div></div>
            </div>
          </Show>
        </TimeSourceProvider>
        <Suspense>
          <HeroSourceProvider value={[heroSrc, setHeroSrc]}>
            <BottomSheet open={!!child()}>{child()}</BottomSheet>
          </HeroSourceProvider>
        </Suspense>
      </Scaffold>
    </>
  );
};

export default Home;
