import { type BitNodeBooleanOptions } from "@nsdefs";
import React from "react";
import {
  Box,
  Button,
  Collapse,
  ListItemButton,
  ListItemText,
  MenuItem,
  Paper,
  Select,
  TextField,
  Tooltip,
  Typography,
} from "@mui/material";
import { OptionSwitch } from "../../ui/React/OptionSwitch";
import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip";
import { ExpandLess, ExpandMore } from "@mui/icons-material";
import { JSONMap } from "../../Types/Jsonable";
import { Settings } from "../../Settings/Settings";
import { Player } from "@player";

interface SourceFileButtonRowProps {
  sfNumber: number;
  sfLevel: number;
  sfActiveLevel: number;
  callbacks: BitNodeAdvancedOptionsProps["callbacks"];
}

function SourceFileButtonRow({
  sfNumber,
  sfLevel,
  sfActiveLevel,
  callbacks,
}: SourceFileButtonRowProps): React.ReactElement {
  const title = `SF-${sfNumber}`;
  const sourceFileLevelTool =
    sfNumber !== 12 ? (
      [...Array(sfLevel + 1).keys()].map((level) => (
        <Button
          key={level}
          onClick={() => {
            callbacks.setSfActiveLevel(sfNumber, level);
          }}
          sx={{
            marginRight: "0.5rem !important",
            border: level === sfActiveLevel ? `1px solid ${Settings.theme.info}` : "",
            minWidth: "40px",
          }}
        >
          {level}
        </Button>
      ))
    ) : (
      // The usage of TextField instead of NumberInput is intentional.
      <TextField
        sx={{ maxWidth: "185px" }}
        value={sfActiveLevel}
        onChange={(event) => {
          // Empty string will be automatically changed to "0".
          if (event.target.value === "") {
            callbacks.setSfActiveLevel(sfNumber, 0);
            return;
          }
          const level = Number.parseInt(event.target.value);
          if (!Number.isFinite(level) || level < 0 || level > sfLevel) {
            return;
          }
          callbacks.setSfActiveLevel(sfNumber, level);
        }}
      ></TextField>
    );
  const extraInfo =
    sfNumber === 12 ? (
      <td>
        <Typography marginLeft="1rem">Max level: {sfLevel}</Typography>
      </td>
    ) : null;

  return (
    <tr>
      <td>
        <Typography>{title}</Typography>
      </td>
      <td>{sourceFileLevelTool}</td>
      {extraInfo}
    </tr>
  );
}

function SourceFileOverrides({
  currentSourceFiles,
  sourceFileOverrides,
  callbacks,
  getSfLevel,
}: {
  currentSourceFiles: BitNodeAdvancedOptionsProps["currentSourceFiles"];
  sourceFileOverrides: BitNodeAdvancedOptionsProps["sourceFileOverrides"];
  callbacks: BitNodeAdvancedOptionsProps["callbacks"];
  getSfLevel: (sfNumber: number) => number;
}): React.ReactElement {
  const firstSourceFile = React.useMemo(
    () => (currentSourceFiles.size > 0 ? [...currentSourceFiles.keys()][0] : null),
    [currentSourceFiles],
  );
  const [selectElementValue, setSelectElementValue] = React.useState<number | null>(firstSourceFile);
  const getMenuItemList = (data: typeof sourceFileOverrides): number[] => {
    return [...currentSourceFiles.keys()].filter((sfNumber) => ![...data.keys()].includes(sfNumber));
  };
  const menuItemList = getMenuItemList(sourceFileOverrides);

  React.useEffect(() => {
    if (sourceFileOverrides.size === 0) {
      setSelectElementValue(firstSourceFile);
    }
  }, [sourceFileOverrides, firstSourceFile]);

  const basicNote = `Changing the active level of a SF is temporary; you still permanently own that SF level. For example, if
  you enter BN 1.3 while having SF 1.2 but with the active level set to 0, you will not get the bonuses from SF
  1.1 or SF 1.2, but you will still earn SF 1.3 when destroying the BN.`;
  const note = currentSourceFiles.has(10) ? (
    <>
      <Typography>Note:</Typography>
      <ul style={{ marginTop: 0 }}>
        <li>{basicNote}</li>
        <li>
          Changing the active level of SF 10 does not affect your current sleeves or the maximum number of sleeves.
        </li>
      </ul>
    </>
  ) : (
    <>
      <Typography>Note: {basicNote}</Typography>
      <br />
    </>
  );

  return (
    <>
      <Typography>Override active level of Source-File:</Typography>
      <br />
      <Typography component="div">{note}</Typography>
      <div>
        <Select
          disabled={menuItemList.length === 0}
          value={selectElementValue ?? ""}
          onChange={(event) => {
            setSelectElementValue(Number(event.target.value));
          }}
          sx={{ minWidth: "80px" }}
        >
          {menuItemList.map((sfNumber) => (
            <MenuItem key={sfNumber} value={sfNumber}>
              SF-{sfNumber}
            </MenuItem>
          ))}
        </Select>
        <Button
          disabled={menuItemList.length === 0}
          onClick={() => {
            if (!selectElementValue) {
              return;
            }
            const newSfOverrides = new JSONMap(sourceFileOverrides);
            newSfOverrides.set(selectElementValue, getSfLevel(selectElementValue));
            const newMenuItemList = getMenuItemList(newSfOverrides);
            if (newMenuItemList.length > 0) {
              setSelectElementValue(newMenuItemList[0]);
            } else {
              setSelectElementValue(null);
            }
            callbacks.setSfOverrides(newSfOverrides);
          }}
          sx={{ marginLeft: "1rem" }}
        >
          Add
        </Button>
        <ButtonWithTooltip
          normalTooltip="Remove all overridden SF"
          disabledTooltip={sourceFileOverrides.size === 0 ? "No overridden SF" : ""}
          onClick={() => {
            callbacks.setSfOverrides(new JSONMap());
          }}
          buttonProps={{ sx: { marginLeft: "1rem" } }}
        >
          Remove all
        </ButtonWithTooltip>
      </div>
      <br />
      {sourceFileOverrides.size > 0 && (
        <>
          <table>
            <tbody>
              <tr>
                <td>
                  <Tooltip title="Set active level for all chosen SF">
                    <Typography minWidth="7rem">Set all SF</Typography>
                  </Tooltip>
                </td>
                <td>
                  {[0, 1, 2, 3].map((level) => (
                    <ButtonWithTooltip
                      key={level}
                      onClick={() => {
                        const newSfOverrides = new JSONMap(sourceFileOverrides);
                        for (const [sfNumber] of newSfOverrides) {
                          newSfOverrides.set(sfNumber, Math.min(level, getSfLevel(sfNumber)));
                        }
                        callbacks.setSfOverrides(newSfOverrides);
                      }}
                      buttonProps={{ sx: { marginRight: "0.5rem", minWidth: "40px" } }}
                    >
                      {level}
                    </ButtonWithTooltip>
                  ))}
                </td>
              </tr>
              {[...sourceFileOverrides.keys()].map((sfNumber) => (
                <SourceFileButtonRow
                  key={sfNumber}
                  sfNumber={sfNumber}
                  sfLevel={getSfLevel(sfNumber)}
                  sfActiveLevel={sourceFileOverrides.get(sfNumber) ?? 0}
                  callbacks={callbacks}
                ></SourceFileButtonRow>
              ))}
            </tbody>
          </table>
          <br />
        </>
      )}
    </>
  );
}

function IntelligenceOverride({
  intelligenceOverride,
  callbacks,
}: {
  intelligenceOverride: BitNodeAdvancedOptionsProps["intelligenceOverride"];
  callbacks: BitNodeAdvancedOptionsProps["callbacks"];
}): React.ReactElement {
  const [enabled, setEnabled] = React.useState<boolean>(false);
  const [lastValueOfIntelligenceOverride, setLastValueOfIntelligenceOverride] = React.useState<number | undefined>(
    Player.skills.intelligence,
  );
  return (
    <OptionSwitch
      disabled={Player.skills.intelligence <= 0}
      checked={false}
      onChange={(value) => {
        setEnabled(value);
        if (!value) {
          // If this option is disabled, save last value and reset data.
          setLastValueOfIntelligenceOverride(intelligenceOverride);
          callbacks.setIntelligenceOverride(undefined);
          return;
        } else {
          // If this option is enabled, load last value.
          callbacks.setIntelligenceOverride(lastValueOfIntelligenceOverride);
        }
      }}
      text={
        <>
          <Typography component="div" display="flex" gap="1rem">
            <Typography display="flex" alignItems="center">
              Override Intelligence:
            </Typography>
            <TextField
              sx={{ maxWidth: "4rem" }}
              disabled={!enabled}
              value={intelligenceOverride !== undefined ? intelligenceOverride : ""}
              onChange={(event) => {
                // Empty string will be automatically changed to "1".
                if (event.target.value === "") {
                  callbacks.setIntelligenceOverride(1);
                  return;
                }
                const value = Number.parseInt(event.target.value);
                if (!Number.isInteger(value) || value < 1) {
                  return;
                }
                callbacks.setIntelligenceOverride(value);
              }}
            ></TextField>
          </Typography>
        </>
      }
      tooltip={
        <>
          <Typography component="div">
            Your intelligence and your Sleeves' intelligence will be temporarily set to this value if it is lower than
            their current values. For example:
            <ul>
              <li>
                If your intelligence is 1000 and you set this value to 500, your intelligence will be temporarily set to
                500.
              </li>
              <li>
                If a Sleeve's intelligence is 200 and you set this value to 500, that Sleeve's intelligence is still
                200.
              </li>
            </ul>
          </Typography>
          <Typography>
            Note that you still gain intelligence experience as normal.
            <br />
            For example, suppose you have 1e6 intelligence exp (intelligence skill = 242) and set the intelligence
            override to 100. At the start of the BitNode, your intelligence skill will be set to 100 (equivalent to
            ~11255.318 intelligence exp).
            <br />
            If you gain 500e3 intelligence exp during the BitNode, your intelligence skill will increase to 220 (total
            intelligence exp = 11255 + 500e3 = 511255). After performing bitflume, the exp gained during the BitNode is
            added to your original exp. Your intelligence skill will then become 255 (total intelligence exp = 1e6 +
            500e3 = 1.5e6).
          </Typography>
          <br />
          <Typography>
            The overridden intelligence will be shown in the character overview. You can hover your mouse over it to see
            the original value.
          </Typography>
        </>
      }
    />
  );
}

interface BitNodeAdvancedOptionsProps {
  targetBitNode: number;
  currentSourceFiles: Map<number, number>;
  sourceFileOverrides: JSONMap<number, number>;
  intelligenceOverride: number | undefined;
  bitNodeBooleanOptions: BitNodeBooleanOptions;
  callbacks: {
    setSfOverrides: (value: JSONMap<number, number>) => void;
    setSfActiveLevel: (sfNumber: number, sfLevel: number) => void;
    setIntelligenceOverride: (value: number | undefined) => void;
    setBooleanOption: (key: keyof BitNodeBooleanOptions, value: boolean) => void;
  };
}

export function BitNodeAdvancedOptions({
  targetBitNode,
  currentSourceFiles,
  sourceFileOverrides,
  intelligenceOverride,
  bitNodeBooleanOptions,
  callbacks,
}: BitNodeAdvancedOptionsProps): React.ReactElement {
  const [open, setOpen] = React.useState(false);
  const getSfLevel = React.useCallback(
    (sfNumber: number): number => {
      return currentSourceFiles.get(sfNumber) ?? 0;
    },
    [currentSourceFiles],
  );

  return (
    <Box component={Paper} sx={{ mt: 1, p: 1 }}>
      <ListItemButton disableGutters onClick={() => setOpen((old) => !old)} sx={{ padding: "4px 8px" }}>
        <ListItemText primary={<Typography variant="h6">Advanced options</Typography>} />
        {open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
      </ListItemButton>
      <Collapse in={open}>
        <Box sx={{ padding: "0 1rem" }}>
          <Typography>
            These options enable unique gameplay that is intended for experienced players. If you are a new player, you
            can safely ignore these options and come back to try them later.
          </Typography>
          <br />
          <OptionSwitch
            checked={bitNodeBooleanOptions.restrictHomePCUpgrade}
            onChange={(value) => {
              callbacks.setBooleanOption("restrictHomePCUpgrade", value);
            }}
            text="Restrict max RAM and core of Home PC"
            tooltip="The home computer's maximum RAM and number of cores are lower than normal. Max RAM: 128GB. Max core: 1."
          />
          <OptionSwitch
            disabled={getSfLevel(2) === 0 && targetBitNode !== 2}
            checked={bitNodeBooleanOptions.disableGang}
            onChange={(value) => {
              callbacks.setBooleanOption("disableGang", value);
            }}
            text="Disable Gang"
            tooltip="Disable Gang, regardless of BitNode and SF level"
          />
          <OptionSwitch
            disabled={getSfLevel(3) === 0 && targetBitNode !== 3}
            checked={bitNodeBooleanOptions.disableCorporation}
            onChange={(value) => {
              callbacks.setBooleanOption("disableCorporation", value);
            }}
            text="Disable Corporation"
            tooltip="Disable Corporation, regardless of BitNode and SF level"
          />
          <OptionSwitch
            disabled={getSfLevel(6) === 0 && getSfLevel(7) === 0 && targetBitNode !== 6 && targetBitNode !== 7}
            checked={bitNodeBooleanOptions.disableBladeburner}
            onChange={(value) => {
              callbacks.setBooleanOption("disableBladeburner", value);
            }}
            text="Disable Bladeburner"
            tooltip="Disable Bladeburner, regardless of BitNode and SF level"
          />
          <OptionSwitch
            checked={bitNodeBooleanOptions.disable4SData}
            onChange={(value) => {
              callbacks.setBooleanOption("disable4SData", value);
            }}
            text="Disable 4S Market Data"
            tooltip="Disable 4S Market Data, regardless of BitNode and SF level"
          />
          <OptionSwitch
            disabled={getSfLevel(9) === 0 && targetBitNode !== 9}
            checked={bitNodeBooleanOptions.disableHacknetServer}
            onChange={(value) => {
              callbacks.setBooleanOption("disableHacknetServer", value);
            }}
            text="Disable Hacknet Server"
            tooltip="Disable Hacknet Server, regardless of BitNode and SF level. Hacknet Node is re-enabled in place of Hacknet Server."
          />
          <OptionSwitch
            disabled={getSfLevel(10) === 0 && targetBitNode !== 10}
            checked={bitNodeBooleanOptions.disableSleeveExpAndAugmentation}
            onChange={(value) => {
              callbacks.setBooleanOption("disableSleeveExpAndAugmentation", value);
            }}
            text="Disable Sleeves' experience and augmentation"
            tooltip="Sleeves cannot gain experience or install augmentations"
          />
          <IntelligenceOverride
            intelligenceOverride={intelligenceOverride}
            callbacks={callbacks}
          ></IntelligenceOverride>
          <br />
          <SourceFileOverrides
            currentSourceFiles={currentSourceFiles}
            sourceFileOverrides={sourceFileOverrides}
            callbacks={callbacks}
            getSfLevel={getSfLevel}
          ></SourceFileOverrides>
        </Box>
      </Collapse>
    </Box>
  );
}
