import type PolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/policyRepresentation";
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
import type {
  Clients,
  PolicyQuery,
} from "@keycloak/keycloak-admin-client/lib/resources/clients";
import { Select, SelectOption, SelectVariant } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";

import { adminClient } from "../../admin-client";
import { useFetch } from "../../utils/useFetch";

type Type = "resources" | "policies";

type ResourcesPolicySelectProps = {
  name: Type;
  clientId: string;
  permissionId?: string;
  variant?: SelectVariant;
  preSelected?: string;
  isRequired?: boolean;
};

type Policies = {
  id?: string;
  name?: string;
};

type TypeMapping = {
  [key in Type]: {
    searchFunction: keyof Pick<Clients, "listPolicies" | "listResources">;
    fetchFunction: keyof Pick<
      Clients,
      "getAssociatedPolicies" | "getAssociatedResources"
    >;
  };
};

const typeMapping: TypeMapping = {
  resources: {
    searchFunction: "listResources",
    fetchFunction: "getAssociatedResources",
  },
  policies: {
    searchFunction: "listPolicies",
    fetchFunction: "getAssociatedPolicies",
  },
};

export const ResourcesPolicySelect = ({
  name,
  clientId,
  permissionId,
  variant = SelectVariant.typeaheadMulti,
  preSelected,
  isRequired = false,
}: ResourcesPolicySelectProps) => {
  const { t } = useTranslation("clients");

  const {
    control,
    formState: { errors },
  } = useFormContext<PolicyRepresentation>();
  const [items, setItems] = useState<Policies[]>([]);
  const [search, setSearch] = useState("");
  const [open, setOpen] = useState(false);

  const functions = typeMapping[name];

  const convert = (
    p: PolicyRepresentation | ResourceRepresentation,
  ): Policies => ({
    id: "_id" in p ? p._id : "id" in p ? p.id : undefined,
    name: p.name,
  });

  useFetch(
    async () => {
      const params: PolicyQuery = Object.assign(
        { id: clientId, first: 0, max: 10, permission: "false" },
        search === "" ? null : { name: search },
      );
      return (
        await Promise.all([
          adminClient.clients[functions.searchFunction](params),
          permissionId
            ? adminClient.clients[functions.fetchFunction]({
                id: clientId,
                permissionId,
              })
            : Promise.resolve([]),
        ])
      )
        .flat()
        .filter(
          (r): r is PolicyRepresentation | ResourceRepresentation =>
            typeof r !== "string",
        )
        .map(convert)
        .filter(
          ({ id }, index, self) =>
            index === self.findIndex(({ id: otherId }) => id === otherId),
        );
    },
    setItems,
    [search],
  );

  const toSelectOptions = () =>
    items.map((p) => (
      <SelectOption key={p.id} value={p.id}>
        {p.name}
      </SelectOption>
    ));

  return (
    <Controller
      name={name}
      defaultValue={preSelected ? [preSelected] : []}
      control={control}
      rules={{ validate: (value) => !isRequired || value!.length > 0 }}
      render={({ field }) => (
        <Select
          toggleId={name}
          variant={variant}
          onToggle={setOpen}
          onFilter={(_, filter) => {
            setSearch(filter);
            return toSelectOptions();
          }}
          onClear={() => {
            field.onChange([]);
            setSearch("");
          }}
          selections={field.value}
          onSelect={(_, selectedValue) => {
            const option = selectedValue.toString();
            if (variant === SelectVariant.typeaheadMulti) {
              const changedValue = field.value?.find(
                (p: string) => p === option,
              )
                ? field.value.filter((p: string) => p !== option)
                : [...field.value!, option];
              field.onChange(changedValue);
            } else {
              field.onChange([option]);
            }

            setSearch("");
          }}
          isOpen={open}
          aria-labelledby={t(name)}
          isDisabled={!!preSelected}
          validated={errors[name] ? "error" : "default"}
          typeAheadAriaLabel={t(name)}
        >
          {toSelectOptions()}
        </Select>
      )}
    />
  );
};
