import clsx from "clsx";
import { Suspense } from "react";
import {
  useLoaderData,
  LoaderFunctionArgs,
  Await,
  useAsyncError,
} from "react-router";
import { PAYSITE_LIST } from "#env/env-vars";
import {
  ARTISTS_OR_CREATORS_LOWERCASE,
  AVAILABLE_PAYSITE_LIST,
} from "#env/derived-vars";
import { createArtistsPageURL } from "#lib/urls";
import { parseOffset } from "#lib/pagination";
import { PageSkeleton } from "#components/pages";
import { FooterAd, HeaderAd, SliderAd } from "#components/advs";
import { Paginator } from "#components/pagination";
import { CardList, ArtistCard } from "#components/cards";
import { ButtonSubmit, FormRouter, FormSection } from "#components/forms";
import { LoadingIcon } from "#components/loading";
import { getArtists } from "#entities/profiles";

import * as styles from "./profiles.module.scss";

interface IProps {
  results: ReturnType<typeof getArtists>;
  query?: string;
  service?: string;
  sort_by?: ISortField;
  order?: "asc" | "desc";
  offset?: number;
  true_count?: number;
}

const sortFields = [
  "favorited",
  "indexed",
  "updated",
  "name",
  "service",
] as const;

type ISortField = (typeof sortFields)[number];

function validateSortField(input: unknown): asserts input is ISortField {
  if (!sortFields.includes(input as ISortField)) {
    throw new Error(`Invalid sort field value "${input}".`);
  }
}

export function ArtistsPage() {
  const { results, query, service, sort_by, order, offset } =
    useLoaderData() as IProps;
  const title = "Artists";
  const heading = "Artists";

  return (
    <PageSkeleton name="artists" title={title} heading={heading}>
      <SliderAd />

      <SearchForm
        query={query}
        service={service}
        sort_by={sort_by}
        order={order}
      />

      <div style={{ textAlign: "center" }}>
        <h2 id="display-status" className="subtitle">
          Displaying cached popular artists
        </h2>
      </div>

      <div className="paginator" id="paginator-top">
        <Suspense fallback={<LoadingIcon />}>
          <Await errorElement={<></>} resolve={results}>
            {(resolvedResult: Awaited<typeof results>) => (
              <Paginator
                count={resolvedResult.count}
                offset={offset}
                constructURL={(offset) => {
                  const url = createArtistsPageURL(
                    offset,
                    query,
                    service,
                    sort_by,
                    order
                  );

                  return String(url);
                }}
              />
            )}
          </Await>
        </Suspense>
      </div>

      <HeaderAd />

      <CardList layout="phone">
        <Suspense
          fallback={
            <p className={styles.loading}>
              <LoadingIcon>Loading creators... please wait!</LoadingIcon>
            </p>
          }
        >
          <Await resolve={results} errorElement={<CollectionError />}>
            {(resolvedResult: Awaited<typeof results>) =>
              resolvedResult.artists.length === 0 ? (
                <p className={clsx("subtitle", "card-list__item--no-results")}>
                  No {ARTISTS_OR_CREATORS_LOWERCASE} found for your query.
                </p>
              ) : (
                resolvedResult.artists.map((artist) => (
                  <ArtistCard
                    key={`${artist.service}-${artist.id}`}
                    artist={artist}
                    isUpdated={sort_by === "updated"}
                    isIndexed={sort_by === "indexed"}
                    isCount={sort_by === "favorited" || sort_by === undefined}
                    isFavorite={artist.isFavourite}
                    singleOf="favorite"
                    pluralOf="favorites"
                  />
                ))
              )
            }
          </Await>
        </Suspense>
      </CardList>

      <div className="paginator" id="paginator-bottom">
        <Suspense fallback={<LoadingIcon />}>
          <Await errorElement={<></>} resolve={results}>
            {(resolvedResult: Awaited<typeof results>) => (
              <Paginator
                count={resolvedResult.count}
                offset={offset}
                constructURL={(offset) => {
                  const url = createArtistsPageURL(
                    offset,
                    query,
                    service,
                    sort_by,
                    order
                  );

                  return String(url);
                }}
              />
            )}
          </Await>
        </Suspense>
      </div>

      <FooterAd />
    </PageSkeleton>
  );
}

interface ISearchFormProps
  extends Pick<IProps, "query" | "service" | "sort_by" | "order"> {}

function SearchForm({ query, service, sort_by, order }: ISearchFormProps) {
  return (
    <FormRouter
      id="search-form"
      className="search-form"
      method="GET"
      autoComplete="off"
      noValidate={true}
      acceptCharset="UTF-8"
      statusSection={null}
      submitButton={undefined}
      style={{ maxWidth: "fit-content" }}
    >
      {(state) => (
        <>
          <FormSection>
            <label htmlFor="q">Name</label>
            <input
              type="text"
              name="q"
              id="q"
              autoComplete="off"
              defaultValue={query}
            />
            <small className="subtitle" style={{ marginLeft: "5px" }}>
              Leave blank to list all
            </small>
          </FormSection>

          <FormSection>
            <label htmlFor="service">Service</label>
            <select id="service" name="service" defaultValue={service}>
              <option value="">All</option>
              {AVAILABLE_PAYSITE_LIST.map((paysite, index) => (
                <option key={index} value={paysite.name}>
                  {paysite.title}
                </option>
              ))}
            </select>
          </FormSection>

          <FormSection>
            <label htmlFor="sort_by">Sort by</label>
            <select id="sort_by" name="sort_by" defaultValue={sort_by}>
              <option value="favorited">Popularity</option>
              <option value="indexed">Date Indexed</option>
              <option value="updated">Date Updated</option>
              <option value="name">Alphabetical Order</option>
              <option value="service">Service</option>
            </select>{" "}
            <select id="order" name="order" defaultValue={order}>
              <option value="desc">Descending</option>
              <option value="asc">Ascending</option>
            </select>
          </FormSection>

          <FormSection>
            <div></div>
            <div style={{ display: "table-cell" }}>
              {state === "loading"
                ? "Loading..."
                : state === "submitting"
                ? "Submitting..."
                : "Ready for submit"}
            </div>
          </FormSection>

          <FormSection>
            <div style={{ display: "table-cell" }}></div>
            <ButtonSubmit
              disabled={state === "loading" || state === "submitting"}
            >
              Search
            </ButtonSubmit>
          </FormSection>
        </>
      )}
    </FormRouter>
  );
}

function CollectionError() {
  const error = useAsyncError();
  console.error(error);

  return (
    <div>
      <p className={styles.error}>Failed to load artists.</p>
      <details>
        <summary>Details</summary>
        {/* @ts-expect-error vague type definition */}
        <p>{error?.statusText || error?.message}</p>
      </details>
    </div>
  );
}

export async function loader({
  request,
}: LoaderFunctionArgs): Promise<IProps> {
  const searchParams = new URL(request.url).searchParams;

  let offset: IProps["offset"] | undefined = undefined;
  {
    const inputOffset = searchParams.get("o")?.trim();

    if (inputOffset) {
      offset = parseOffset(inputOffset);
    }
  }

  let query: IProps["query"] | undefined = searchParams.get("q")?.trim();

  let sort_by: IProps["sort_by"] | undefined = undefined;
  {
    const inputValue = searchParams.get("sort_by")?.trim();

    if (inputValue) {
      validateSortField(inputValue);
      sort_by = inputValue;
    }
  }

  let order_by: IProps["order"] | undefined = undefined;
  {
    const inputValue = searchParams.get("order")?.trim();

    if (inputValue) {
      if (inputValue !== "asc" && inputValue !== "desc") {
        throw new Error(`Invalid order by field "${inputValue}".`);
      }

      order_by = inputValue;
    }
  }

  let service: IProps["service"] = undefined;
  {
    const inputValue = searchParams.get("service")?.trim();

    if (inputValue) {
      if (!PAYSITE_LIST.includes(inputValue)) {
        throw new Error(`Unknown service "${inputValue}".`);
      }
    }

    service = inputValue;
  }

  const results = getArtists({
    offset,
    order: order_by,
    service,
    sort_by,
    query,
  });

  const pageProps = {
    results,
    sort_by,
    order: order_by,
    offset,
    service,
    query,
  } satisfies IProps;

  return pageProps;
}
