import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router';

import { Box, Button, Menu, MenuItem, Typography } from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { Pagination } from '@northern.tech/common-ui';
import { makeStyles } from 'tss-react/mui';

import { gray } from '../../../themes/common';
import Spinner from '../../common/Spinner';
import { ModuleSummary } from '../components/ModuleSummary';
import { useAppDispatch, useAppSelector } from '../store';
import { fetchModulesList } from '../store/modulesSlice/thunks';
import { selectModulesList, selectModulesLoading, selectModulesTotal } from '../store/modulesSlice/selectors';
import { loadProjectInfo } from '../store/projectsSlice/thunks';
import { selectCurrentProject } from '../store/projectsSlice/selectors';
import { formatModuleDate } from '../utils/format';

const RELEVANCE = 'Relevance';
const ALPHABETICALLY = 'Alphabetically';
const MOST_DOWNLOADS = 'Most downloads';
const MOST_RECENT = 'Most recent';

const ROWS_PER_PAGE = [5, 10, 25, 50, 100];
const DEFAULT_PER_PAGE = 10;

const SORT: Record<string, { sortColumn: string; sortDescending: string }> = {
  [ALPHABETICALLY]: { sortColumn: 'name', sortDescending: 'false' },
  [RELEVANCE]: { sortColumn: 'relevance', sortDescending: 'true' },
  [MOST_DOWNLOADS]: { sortColumn: 'downloads', sortDescending: 'true' },
  [MOST_RECENT]: { sortColumn: 'updated', sortDescending: 'true' }
};

const useStyles = makeStyles()(theme => {
  // Solid @Gray300 in light; a soft white-alpha in dark (legacy `.modules-item`
  // border), since a solid light gray reads as a glaring near-white line on dark.
  const cardBorder = theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.38)' : gray[300];
  return {
    root: { maxWidth: 864 },
    top: {
      margin: '34px 0 24px 0',
      display: 'flex',
      justifyContent: 'space-between',
      alignItems: 'baseline'
    },
    item: {
      border: `1px solid ${cardBorder}`,
      padding: '26px 35px 25px 19px',
      borderRadius: 4,
      marginBottom: 16,
      display: 'flex',
      justifyContent: 'space-between'
    },
    summary: { maxWidth: 610 },
    rightInfo: { textAlign: 'right', minWidth: 170, '& > div': { marginBottom: 12 } },
    dropdownButton: {
      border: `1px solid ${cardBorder}`,
      borderRadius: 4,
      padding: '7px 14px',
      fontWeight: 500,
      color: theme.palette.text.primary,
      '&:hover': { border: `1px solid ${cardBorder}`, backgroundColor: theme.palette.action.hover }
    },
    empty: { marginTop: 24 }
  };
});

export const ModulesList = () => {
  const { classes } = useStyles();
  const dispatch = useAppDispatch();
  const [searchParams, setSearchParams] = useSearchParams();

  const modules = useAppSelector(selectModulesList);
  const total = useAppSelector(selectModulesTotal);
  const loading = useAppSelector(selectModulesLoading);
  const project = useAppSelector(selectCurrentProject);

  const searchQuery = searchParams.get('query') ?? '';
  const defaultOrderBy = searchQuery ? RELEVANCE : ALPHABETICALLY;
  const orderByParam = searchParams.get('orderBy') ?? '';
  // Pagination and sorting live in the URL so a reload (or a shared link) keeps
  // the user on the same page of results.
  const orderBy = SORT[orderByParam] ? orderByParam : defaultOrderBy;
  const page = Math.max(1, Number(searchParams.get('page')) || 1);
  const perPage = ROWS_PER_PAGE.includes(Number(searchParams.get('perPage')))
    ? Number(searchParams.get('perPage'))
    : DEFAULT_PER_PAGE;

  const [sortAnchor, setSortAnchor] = useState<null | HTMLElement>(null);

  const updateParams = useCallback(
    (changes: Record<string, string | number | null>) => {
      setSearchParams(
        previous => {
          const next = new URLSearchParams(previous);
          Object.entries(changes).forEach(([key, value]) => {
            if (value === null || value === '') next.delete(key);
            else next.set(key, String(value));
          });
          return next;
        },
        { replace: true }
      );
    },
    [setSearchParams]
  );

  // Ensure the aside/header have a project even when we arrive straight at /modules.
  useEffect(() => {
    if (!project) {
      const stored = localStorage.getItem('projectID');
      if (stored) dispatch(loadProjectInfo(stored));
    }
  }, [project, dispatch]);

  useEffect(() => {
    const sort = SORT[orderBy];
    const params: Record<string, string | number> = {
      sortColumn: sort.sortColumn,
      sortDescending: sort.sortDescending,
      limit: perPage,
      skip: (page - 1) * perPage
    };
    if (searchQuery) params.searchQuery = searchQuery;
    dispatch(fetchModulesList(params));
  }, [orderBy, perPage, page, searchQuery, dispatch]);

  const startIndex = total > 0 ? (page - 1) * perPage + 1 : 0;
  const endIndex = Math.min(startIndex + perPage - 1, total);

  const sortOptions = useMemo(
    () => [ALPHABETICALLY, ...(searchQuery ? [RELEVANCE] : []), MOST_DOWNLOADS, MOST_RECENT],
    [searchQuery]
  );

  return (
    <Box className={classes.root}>
      <Box className={classes.top}>
        <Typography component="span" variant="subtitle1">
          Showing{' '}
          <b>
            {startIndex}-{endIndex}
          </b>{' '}
          of <b>{total}</b>
          {searchQuery && (
            <>
              {' '}
              results for <b>{searchQuery}</b>
            </>
          )}
        </Typography>
        <Button
          color="inherit"
          className={classes.dropdownButton}
          endIcon={<ExpandMoreIcon />}
          onClick={e => setSortAnchor(e.currentTarget)}
        >
          {orderBy}
        </Button>
        <Menu anchorEl={sortAnchor} open={Boolean(sortAnchor)} onClose={() => setSortAnchor(null)}>
          {sortOptions.map(option => (
            <MenuItem
              key={option}
              selected={option === orderBy}
              onClick={() => {
                updateParams({ orderBy: option, page: null });
                setSortAnchor(null);
              }}
            >
              {option}
            </MenuItem>
          ))}
        </Menu>
      </Box>

      {loading && <Spinner />}

      {modules.map(module => (
        <Box key={module.name} className={classes.item}>
          <ModuleSummary module={module} className={classes.summary} />
          <Box className={classes.rightInfo}>
            <div>Version: {module.version}</div>
            <div>Updated: {formatModuleDate(module.updated)}</div>
            <div>Total downloads: {module.downloads}</div>
          </Box>
        </Box>
      ))}

      {!loading && modules.length === 0 && (
        <Typography className={classes.empty}>
          {searchQuery ? `No modules match "${searchQuery}".` : 'No modules available.'}
        </Typography>
      )}

      <Pagination
        count={total}
        page={page}
        rowsPerPage={perPage}
        rowsPerPageOptions={ROWS_PER_PAGE}
        onChangePage={nextPage => updateParams({ page: nextPage })}
        onChangeRowsPerPage={rows => updateParams({ perPage: rows, page: null })}
      />
    </Box>
  );
};
