import clsx from "clsx";
import { Suspense, useRef, useState } 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 />

      <HeaderAd />

      <div className="paginator" id="paginator-top">
        <SearchForm
          query={query}
          service={service}
          sort_by={sort_by}
          order={order}
        />
        <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>

      <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) {
  const sortRef = useRef<HTMLInputElement>(null);
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
  const [sortDirection, setSortDirection] = useState<string | undefined>(order);

  const onSortChange = (e: React.MouseEvent) => {
    e.preventDefault();
    setSortDirection(sortDirection === "asc" ? "desc" : "asc");
    if (sortRef.current) {
      sortRef.current.value = sortDirection === "asc" ? "desc" : "asc";
      sortRef.current.form?.requestSubmit();
    }
  }
  const onSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => e.currentTarget.form?.requestSubmit();
  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (timeoutRef.current) clearTimeout(timeoutRef.current);

    const target = e.currentTarget as HTMLInputElement;

    timeoutRef.current = setTimeout(() => {
      if (target.form) target.form.requestSubmit();
    }, 500);
  };

  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) => (
        <>
          <div className="wrapper">
            <input
              type="text"
              name="q"
              id="q"
              autoComplete="off"
              defaultValue={query}
              placeholder="Search..."
              onChange={onInputChange}
            />
            <ButtonSubmit
              disabled={state === "loading" || state === "submitting"}
              className="search-button"
            >
              <img src='/static/menu/search.svg' />
            </ButtonSubmit>
          </div>
          <div className="wrapper">
            <select className="service" name="service" defaultValue={service} onChange={onSelectChange}>
              <option value="">Services</option>
              {AVAILABLE_PAYSITE_LIST.map((paysite, index) => (
                <option key={index} value={paysite.name}>
                  {paysite.title}
                </option>
              ))}
            </select>
            <select className="sort_by" name="sort_by" defaultValue={sort_by} onChange={onSelectChange}>
              <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>
            <button onClick={onSortChange} className="sort_dir button" title={sortDirection === "asc" ? "Ascending" : "Descending"} >
              <img src="/static/sort.svg" alt="Sort" className={sortDirection} />
            </button>
          </div>
          <input type="text" name="order" className="hidden" ref={sortRef} />
        </>
      )}
    </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;
}
