import {
  Accessor,
  createContext,
  createMemo,
  createRenderEffect,
  createResource,
  untrack,
  useContext,
} from "solid-js";
import { Account } from "../accounts/stores";
import { createRestAPIClient, mastodon } from "masto";
import { useLocation } from "@solidjs/router";
import { useNavigator } from "~platform/StackedRouter";

const restfulCache: Record<string, mastodon.rest.Client> = {};

export function createMastoClientFor(account: Account) {
  const cacheKey = [account.site, account.accessToken].join("");
  const cache = restfulCache[cacheKey];
  if (cache) return cache;

  const client = createRestAPIClient({
    url: account.site,
    accessToken: account.accessToken,
  });
  restfulCache[cacheKey] = client;

  return client;
}

export function createUnauthorizedClient(site: string) {
  const cache = restfulCache[site];
  if (cache) return cache;

  const client = createRestAPIClient({
    url: site,
  });
  restfulCache[site] = client;

  return client;
}

export function useInstance(client: Accessor<mastodon.rest.Client>) {
  return createResource(client, async (client) => {
    return await client.v2.instance.fetch();
  });
}

export type Session = {
  account: Account;
  client: mastodon.rest.Client;
};

const Context =
  /* @__PURE__ */ createContext<Accessor<readonly Readonly<Session>[]>>();

export const Provider = Context.Provider;

export function useSessions() {
  const sessions = useSessionsRaw();
  const { push } = useNavigator();
  const location = useLocation();

  createRenderEffect(() => {
    if (untrack(() => sessions().length) > 0) return;

    push("/accounts/sign-in?back=" + encodeURIComponent(location.pathname), {
      replace: true,
    });
  });

  return sessions;
}

function useSessionsRaw() {
  const store = useContext(Context);
  if (!store) {
    throw new TypeError("sessions are not provided");
  }
  return store;
}

const DefaultSessionContext = /* @__PURE__ */ createContext<Accessor<number>>(
  () => 0,
);

export const DefaultSessionProvider = DefaultSessionContext.Provider;

/**
 * Return the default session (the first session).
 *
 * This function may return `undefined`, but it will try to redirect the user to the sign in.
 */
export function useDefaultSession() {
  const sessions = useSessions();
  const sessionIndex = useContext(DefaultSessionContext);

  return () => {
    if (sessions().length > 0) {
      return sessions()[sessionIndex()];
    }
  };
}

/**
 * Get a session for the specific acct string.
 *
 * Acct string is a string in the pattern of `{username}@{site_with_protocol}`,
 * like `@thislight@https://mastodon.social`, can be used to identify (tempoarily)
 * an session on the tutu instance.
 *
 * The `site_with_protocol` is required.
 *
 * - If the username is present, the session matches the username and the site is returned; or,
 * - If the username is not present, any session on the site is returned; or,
 * - If no available session available for the pattern, an unauthorised session is returned.
 *
 * In an unauthorised session, the `.account` is {@link RemoteServer} and the `client` is an
 * unauthorised client for the site. This client may not available for some operations.
 */
export function useSessionForAcctStr(acct: Accessor<string>) {
  const allSessions = useSessions();

  return createMemo(() => {
    const [inputUsername, inputSite] = acct().split("@", 2);
    const authedSession = allSessions().find(
      (x) =>
        x.account.site === inputSite &&
        x.account.inf?.username === inputUsername,
    );
    return (
      authedSession ?? {
        client: createUnauthorizedClient(inputSite),
        account: { site: inputSite }, // TODO: we need some security checks here?
      }
    );
  });
}

export function makeAcctText(session: Session) {
  return `${session.account.inf?.username}@${session.account.site}`;
}