import * as React from "react";
import gql from "graphql-tag";
import { useMutation } from "@apollo/client";
import {
  Button,
  Box,
  FormControl,
  FormErrorMessage,
  FormHelperText,
  FormLabel,
  HStack,
  Modal,
  ModalBody,
  ModalCloseButton,
  ModalContent,
  ModalFooter,
  ModalHeader,
  ModalOverlay,
  Radio,
  RadioGroup,
  Spinner,
  useDisclosure,
  useToast,
  CheckboxGroup,
  VStack,
  Checkbox,
} from "@chakra-ui/react";
import { ChevronRightIcon, ExternalLinkIcon } from "@chakra-ui/icons";

import AppearanceLayerSupportUploadModal from "./AppearanceLayerSupportUploadModal";
import Metadata, { MetadataLabel, MetadataValue } from "./Metadata";
import { OutfitLayers } from "../../components/OutfitPreview";
import SpeciesColorPicker from "../../components/SpeciesColorPicker";
import useOutfitAppearance, {
  itemAppearanceFragment,
} from "../../components/useOutfitAppearance";
import useSupport from "./useSupport";

/**
 * AppearanceLayerSupportModal offers Support info and tools for a specific item
 * appearance layer. Open it by clicking a layer from ItemSupportDrawer.
 */
function AppearanceLayerSupportModal({
  item, // Specify this or `petAppearance`
  petAppearance, // Specify this or `item`
  layer,
  outfitState, // speciesId, colorId, pose
  isOpen,
  onClose,
}) {
  const [selectedBodyId, setSelectedBodyId] = React.useState(layer.bodyId);
  const [selectedKnownGlitches, setSelectedKnownGlitches] = React.useState(
    layer.knownGlitches,
  );

  const [previewBiology, setPreviewBiology] = React.useState({
    speciesId: outfitState.speciesId,
    colorId: outfitState.colorId,
    pose: outfitState.pose,
    isValid: true,
  });
  const [uploadModalIsOpen, setUploadModalIsOpen] = React.useState(false);
  const { supportSecret } = useSupport();
  const toast = useToast();

  const parentName = item
    ? item.name
    : `${petAppearance.color.name} ${petAppearance.species.name} ${petAppearance.id}`;

  const [mutate, { loading: mutationLoading, error: mutationError }] =
    useMutation(
      gql`
        mutation ApperanceLayerSupportSetLayerBodyId(
          $layerId: ID!
          $bodyId: ID!
          $knownGlitches: [AppearanceLayerKnownGlitch!]!
          $supportSecret: String!
          $outfitSpeciesId: ID!
          $outfitColorId: ID!
          $formPreviewSpeciesId: ID!
          $formPreviewColorId: ID!
        ) {
          setLayerBodyId(
            layerId: $layerId
            bodyId: $bodyId
            supportSecret: $supportSecret
          ) {
            # This mutation returns the affected AppearanceLayer. Fetch the
            # updated fields, including the appearance on the outfit pet and the
            # form preview pet, to automatically update our cached appearance in
            # the rest of the app. That means you should be able to see your
            # changes immediately!
            id
            bodyId
            item {
              id
              appearanceOnOutfit: appearanceOn(
                speciesId: $outfitSpeciesId
                colorId: $outfitColorId
              ) {
                ...ItemAppearanceForOutfitPreview
              }

              appearanceOnFormPreviewPet: appearanceOn(
                speciesId: $formPreviewSpeciesId
                colorId: $formPreviewColorId
              ) {
                ...ItemAppearanceForOutfitPreview
              }
            }
          }

          setLayerKnownGlitches(
            layerId: $layerId
            knownGlitches: $knownGlitches
            supportSecret: $supportSecret
          ) {
            id
            knownGlitches
            svgUrl # Affected by OFFICIAL_SVG_IS_INCORRECT
          }
        }
        ${itemAppearanceFragment}
      `,
      {
        variables: {
          layerId: layer.id,
          bodyId: selectedBodyId,
          knownGlitches: selectedKnownGlitches,
          supportSecret,
          outfitSpeciesId: outfitState.speciesId,
          outfitColorId: outfitState.colorId,
          formPreviewSpeciesId: previewBiology.speciesId,
          formPreviewColorId: previewBiology.colorId,
        },
        onCompleted: () => {
          onClose();
          toast({
            status: "success",
            title: `Saved layer ${layer.id}: ${parentName}`,
          });
        },
      },
    );

  // TODO: Would be nicer to just learn the correct URL from the server, but we
  //       don't happen to be saving it, and it would be extra stuff to put on
  //       the GraphQL request for non-Support users. We could also just try
  //       loading them, but, ehhh…
  const [newManifestUrl, oldManifestUrl] = convertSwfUrlToPossibleManifestUrls(
    layer.swfUrl,
  );

  return (
    <Modal size="xl" isOpen={isOpen} onClose={onClose}>
      <ModalOverlay>
        <ModalContent>
          <ModalHeader>
            Layer {layer.id}: {parentName}
          </ModalHeader>
          <ModalCloseButton />
          <ModalBody>
            <Metadata>
              <MetadataLabel>DTI ID:</MetadataLabel>
              <MetadataValue>{layer.id}</MetadataValue>
              <MetadataLabel>Neopets ID:</MetadataLabel>
              <MetadataValue>{layer.remoteId}</MetadataValue>
              <MetadataLabel>Zone:</MetadataLabel>
              <MetadataValue>
                {layer.zone.label} ({layer.zone.id})
              </MetadataValue>
              <MetadataLabel>Assets:</MetadataLabel>
              <MetadataValue>
                <HStack spacing="2">
                  <Button
                    as="a"
                    size="xs"
                    target="_blank"
                    href={newManifestUrl}
                    colorScheme="teal"
                  >
                    Manifest (new) <ExternalLinkIcon ml="1" />
                  </Button>
                  <Button
                    as="a"
                    size="xs"
                    target="_blank"
                    href={oldManifestUrl}
                    colorScheme="teal"
                  >
                    Manifest (old) <ExternalLinkIcon ml="1" />
                  </Button>
                </HStack>
                <HStack spacing="2" marginTop="1">
                  {layer.canvasMovieLibraryUrl ? (
                    <Button
                      as="a"
                      size="xs"
                      target="_blank"
                      href={layer.canvasMovieLibraryUrl}
                      colorScheme="teal"
                    >
                      Movie <ExternalLinkIcon ml="1" />
                    </Button>
                  ) : (
                    <Button size="xs" isDisabled>
                      No Movie
                    </Button>
                  )}
                  {layer.svgUrl ? (
                    <Button
                      as="a"
                      size="xs"
                      target="_blank"
                      href={layer.svgUrl}
                      colorScheme="teal"
                    >
                      SVG <ExternalLinkIcon ml="1" />
                    </Button>
                  ) : (
                    <Button size="xs" isDisabled>
                      No SVG
                    </Button>
                  )}
                  {layer.imageUrl ? (
                    <Button
                      as="a"
                      size="xs"
                      target="_blank"
                      href={layer.imageUrl}
                      colorScheme="teal"
                    >
                      PNG <ExternalLinkIcon ml="1" />
                    </Button>
                  ) : (
                    <Button size="xs" isDisabled>
                      No PNG
                    </Button>
                  )}
                  <Button
                    as="a"
                    size="xs"
                    target="_blank"
                    href={layer.swfUrl}
                    colorScheme="teal"
                  >
                    SWF <ExternalLinkIcon ml="1" />
                  </Button>
                  <Box flex="1 1 0" />
                  {item && (
                    <>
                      <Button
                        size="xs"
                        colorScheme="gray"
                        onClick={() => setUploadModalIsOpen(true)}
                      >
                        Upload PNG <ChevronRightIcon />
                      </Button>
                      <AppearanceLayerSupportUploadModal
                        item={item}
                        layer={layer}
                        isOpen={uploadModalIsOpen}
                        onClose={() => setUploadModalIsOpen(false)}
                      />
                    </>
                  )}
                </HStack>
              </MetadataValue>
            </Metadata>
            <Box height="8" />
            {item && (
              <>
                <AppearanceLayerSupportPetCompatibilityFields
                  item={item}
                  layer={layer}
                  outfitState={outfitState}
                  selectedBodyId={selectedBodyId}
                  previewBiology={previewBiology}
                  onChangeBodyId={setSelectedBodyId}
                  onChangePreviewBiology={setPreviewBiology}
                />
                <Box height="8" />
              </>
            )}
            <AppearanceLayerSupportKnownGlitchesFields
              selectedKnownGlitches={selectedKnownGlitches}
              onChange={setSelectedKnownGlitches}
            />
          </ModalBody>
          <ModalFooter>
            {item && (
              <AppearanceLayerSupportModalRemoveButton
                item={item}
                layer={layer}
                outfitState={outfitState}
                onRemoveSuccess={onClose}
              />
            )}
            <Box flex="1 0 0" />
            {mutationError && (
              <Box
                color="red.400"
                fontSize="sm"
                marginLeft="8"
                marginRight="2"
                textAlign="right"
              >
                {mutationError.message}
              </Box>
            )}
            <Button
              isLoading={mutationLoading}
              colorScheme="green"
              onClick={() =>
                mutate().catch((e) => {
                  /* Discard errors here; we'll show them in the UI! */
                })
              }
              flex="0 0 auto"
            >
              Save changes
            </Button>
          </ModalFooter>
        </ModalContent>
      </ModalOverlay>
    </Modal>
  );
}

function AppearanceLayerSupportPetCompatibilityFields({
  item,
  layer,
  outfitState,
  selectedBodyId,
  previewBiology,
  onChangeBodyId,
  onChangePreviewBiology,
}) {
  const [selectedBiology, setSelectedBiology] = React.useState(previewBiology);

  const {
    loading,
    error,
    visibleLayers,
    bodyId: appearanceBodyId,
  } = useOutfitAppearance({
    speciesId: previewBiology.speciesId,
    colorId: previewBiology.colorId,
    pose: previewBiology.pose,
    wornItemIds: [item.id],
  });

  const biologyLayers = visibleLayers.filter((l) => l.source === "pet");

  // After we touch a species/color selector and null out `bodyId`, when the
  // appearance body ID loads in, select it as the new body ID.
  //
  // This might move the radio button away from "all pets", but I think that's
  // a _less_ surprising experience: if you're touching the pickers, then
  // that's probably where you head is.
  React.useEffect(() => {
    if (selectedBodyId == null && appearanceBodyId != null) {
      onChangeBodyId(appearanceBodyId);
    }
  }, [selectedBodyId, appearanceBodyId, onChangeBodyId]);

  return (
    <FormControl isInvalid={error || !selectedBiology.isValid ? true : false}>
      <FormLabel fontWeight="bold">Pet compatibility</FormLabel>
      <RadioGroup
        colorScheme="green"
        value={selectedBodyId}
        onChange={(newBodyId) => onChangeBodyId(newBodyId)}
        marginBottom="4"
      >
        <Radio value="0">
          Fits all pets{" "}
          <Box display="inline" color="gray.400" fontSize="sm">
            (Body ID: 0)
          </Box>
        </Radio>
        <Radio as="div" value={appearanceBodyId} marginTop="2">
          Fits all pets with the same body as:{" "}
          <Box display="inline" color="gray.400" fontSize="sm">
            (Body ID:{" "}
            {appearanceBodyId == null ? (
              <Spinner size="sm" />
            ) : (
              appearanceBodyId
            )}
            )
          </Box>
        </Radio>
      </RadioGroup>
      <Box display="flex" flexDirection="column" alignItems="center">
        <Box
          width="150px"
          height="150px"
          marginTop="2"
          marginBottom="2"
          boxShadow="md"
          borderRadius="md"
        >
          <OutfitLayers
            loading={loading}
            visibleLayers={[...biologyLayers, layer]}
          />
        </Box>
        <SpeciesColorPicker
          speciesId={selectedBiology.speciesId}
          colorId={selectedBiology.colorId}
          idealPose={outfitState.pose}
          size="sm"
          showPlaceholders
          onChange={(species, color, isValid, pose) => {
            const speciesId = species.id;
            const colorId = color.id;

            setSelectedBiology({ speciesId, colorId, isValid, pose });
            if (isValid) {
              onChangePreviewBiology({ speciesId, colorId, isValid, pose });

              // Also temporarily null out the body ID. We'll switch to the new
              // body ID once it's loaded.
              onChangeBodyId(null);
            }
          }}
        />
        <Box height="1" />
        {!error && (
          <FormHelperText>
            If it doesn't look right, try some other options until it does!
          </FormHelperText>
        )}
        {error && <FormErrorMessage>{error.message}</FormErrorMessage>}
      </Box>
    </FormControl>
  );
}

function AppearanceLayerSupportKnownGlitchesFields({
  selectedKnownGlitches,
  onChange,
}) {
  return (
    <FormControl>
      <FormLabel fontWeight="bold">Known glitches</FormLabel>
      <CheckboxGroup value={selectedKnownGlitches} onChange={onChange}>
        <VStack spacing="2" align="flex-start">
          <Checkbox value="OFFICIAL_SWF_IS_INCORRECT">
            Official SWF is incorrect{" "}
            <Box display="inline" color="gray.400" fontSize="sm">
              (Will display a message)
            </Box>
          </Checkbox>
          <Checkbox value="OFFICIAL_SVG_IS_INCORRECT">
            Official SVG is incorrect{" "}
            <Box display="inline" color="gray.400" fontSize="sm">
              (Will use the PNG instead)
            </Box>
          </Checkbox>
          <Checkbox value="OFFICIAL_MOVIE_IS_INCORRECT">
            Official Movie is incorrect{" "}
            <Box display="inline" color="gray.400" fontSize="sm">
              (Will display a message)
            </Box>
          </Checkbox>
          <Checkbox value="DISPLAYS_INCORRECTLY_BUT_CAUSE_UNKNOWN">
            Displays incorrectly, but cause unknown{" "}
            <Box display="inline" color="gray.400" fontSize="sm">
              (Will display a vague message)
            </Box>
          </Checkbox>
          <Checkbox value="OFFICIAL_BODY_ID_IS_INCORRECT">
            Fits all pets on-site, but should not{" "}
            <Box display="inline" color="gray.400" fontSize="sm">
              (TNT's fault. Will show a message, and keep the compatibility
              settings above.)
            </Box>
          </Checkbox>
          <Checkbox value="REQUIRES_OTHER_BODY_SPECIFIC_ASSETS">
            Only fits pets with other body-specific assets{" "}
            <Box display="inline" color="gray.400" fontSize="sm">
              (DTI's fault: bodyId=0 is a lie! Will mark incompatible for some
              pets.)
            </Box>
          </Checkbox>
        </VStack>
      </CheckboxGroup>
    </FormControl>
  );
}

function AppearanceLayerSupportModalRemoveButton({
  item,
  layer,
  outfitState,
  onRemoveSuccess,
}) {
  const { isOpen, onOpen, onClose } = useDisclosure();
  const toast = useToast();
  const { supportSecret } = useSupport();

  const [mutate, { loading, error }] = useMutation(
    gql`
      mutation AppearanceLayerSupportRemoveButton(
        $layerId: ID!
        $itemId: ID!
        $outfitSpeciesId: ID!
        $outfitColorId: ID!
        $supportSecret: String!
      ) {
        removeLayerFromItem(
          layerId: $layerId
          itemId: $itemId
          supportSecret: $supportSecret
        ) {
          # This mutation returns the affected layer, and the affected item.
          # Fetch the updated appearance for the current outfit, which should
          # no longer include this layer. This means you should be able to see
          # your changes immediately!
          item {
            id
            appearanceOn(speciesId: $outfitSpeciesId, colorId: $outfitColorId) {
              ...ItemAppearanceForOutfitPreview
            }
          }

          # The layer's item should be null now, fetch to confirm and update!
          layer {
            id
            item {
              id
            }
          }
        }
      }
      ${itemAppearanceFragment}
    `,
    {
      variables: {
        layerId: layer.id,
        itemId: item.id,
        outfitSpeciesId: outfitState.speciesId,
        outfitColorId: outfitState.colorId,
        supportSecret,
      },
      onCompleted: () => {
        onClose();
        onRemoveSuccess();
        toast({
          status: "success",
          title: `Removed layer ${layer.id} from ${item.name}`,
        });
      },
    },
  );

  return (
    <>
      <Button colorScheme="red" flex="0 0 auto" onClick={onOpen}>
        Remove
      </Button>
      <Modal isOpen={isOpen} onClose={onClose} size="xl" isCentered>
        <ModalOverlay>
          <ModalContent>
            <ModalCloseButton />
            <ModalHeader>
              Remove Layer {layer.id} ({layer.zone.label}) from {item.name}?
            </ModalHeader>
            <ModalBody>
              <Box as="p" marginBottom="4">
                This will permanently-ish remove Layer {layer.id} (
                {layer.zone.label}) from this item.
              </Box>
              <Box as="p" marginBottom="4">
                If you remove a correct layer by mistake, re-modeling should fix
                it, or Matchu can restore it if you write down the layer ID
                before proceeding!
              </Box>
              <Box as="p" marginBottom="4">
                Are you sure you want to remove Layer {layer.id} from this item?
              </Box>
            </ModalBody>
            <ModalFooter>
              <Button flex="0 0 auto" onClick={onClose}>
                Close
              </Button>
              <Box flex="1 0 0" />
              {error && (
                <Box
                  color="red.400"
                  fontSize="sm"
                  marginLeft="8"
                  marginRight="2"
                  textAlign="right"
                >
                  {error.message}
                </Box>
              )}
              <Button
                colorScheme="red"
                flex="0 0 auto"
                onClick={() =>
                  mutate().catch((e) => {
                    /* Discard errors here; we'll show them in the UI! */
                  })
                }
                isLoading={loading}
              >
                Yes, remove permanently
              </Button>
            </ModalFooter>
          </ModalContent>
        </ModalOverlay>
      </Modal>
    </>
  );
}

const SWF_URL_PATTERN =
  /^https?:\/\/images\.neopets\.com\/cp\/(bio|items)\/swf\/(.+?)_([a-z0-9]+)\.swf$/;

function convertSwfUrlToPossibleManifestUrls(swfUrl) {
  const match = new URL(swfUrl, "https://images.neopets.com")
    .toString()
    .match(SWF_URL_PATTERN);
  if (!match) {
    throw new Error(`unexpected SWF URL format: ${JSON.stringify(swfUrl)}`);
  }

  const type = match[1];
  const folders = match[2];
  const hash = match[3];

  // TODO: There are a few potential manifest URLs in play! Long-term, we
  //       should get this from modeling data. But these are some good guesses!
  return [
    `https://images.neopets.com/cp/${type}/data/${folders}/manifest.json`,
    `https://images.neopets.com/cp/${type}/data/${folders}_${hash}/manifest.json`,
  ];
}

export default AppearanceLayerSupportModal;