import React, { useState } from 'react';
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
import List, { ListCards } from 'shared/components/List';
import Card from 'shared/components/Card';
import CardComposer from 'shared/components/CardComposer';
import AddList from 'shared/components/AddList';
import {
  isPositionChanged,
  getSortedDraggables,
  getNewDraggablePosition,
  getAfterDropDraggableList,
} from 'shared/utils/draggables';
import dayjs from 'dayjs';
import { TaskSorting, TaskSortingType, TaskSortingDirection, sortTasks } from 'shared/utils/sorting';

import { Container, BoardContainer, BoardWrapper } from './Styles';
import shouldMetaFilter from './metaFilter';

export enum TaskMeta {
  NONE,
  TITLE,
  MEMBER,
  LABEL,
  DUE_DATE,
}

export enum TaskMetaMatch {
  MATCH_ANY,
  MATCH_ALL,
}

export enum TaskStatus {
  ALL,
  COMPLETE,
  INCOMPLETE,
}

export enum TaskSince {
  ALL,
  TODAY,
  YESTERDAY,
  ONE_WEEK,
  TWO_WEEKS,
  THREE_WEEKS,
}

export type TaskStatusFilter = {
  status: TaskStatus;
  since: TaskSince;
};

export interface TaskMetaFilterName {
  meta: TaskMeta;
  value?: string | dayjs.Dayjs | null;
  id?: string | null;
}

export type TaskNameMetaFilter = {
  name: string;
};

export enum DueDateFilterType {
  TODAY,
  TOMORROW,
  THIS_WEEK,
  NEXT_WEEK,
  ONE_WEEK,
  TWO_WEEKS,
  THREE_WEEKS,
  OVERDUE,
  NO_DUE_DATE,
}

export type DueDateMetaFilter = {
  type: DueDateFilterType;
  label: string;
};

export type MemberMetaFilter = {
  id: string;
  username: string;
};

export type LabelMetaFilter = {
  id: string;
  name: string;
  color: string;
};

export type TaskMetaFilters = {
  match: TaskMetaMatch;
  dueDate: DueDateMetaFilter | null;
  taskName: TaskNameMetaFilter | null;
  members: Array<MemberMetaFilter>;
  labels: Array<LabelMetaFilter>;
};

function shouldStatusFilter(task: Task, filter: TaskStatusFilter) {
  if (filter.status === TaskStatus.ALL) {
    return true;
  }

  if (filter.status === TaskStatus.INCOMPLETE && task.complete === false) {
    return true;
  }
  if (filter.status === TaskStatus.COMPLETE && task.completedAt && task.complete === true) {
    const completedAt = dayjs(task.completedAt);
    const REFERENCE = dayjs();
    switch (filter.since) {
      case TaskSince.TODAY:
        const TODAY = REFERENCE.clone().startOf('day');
        return completedAt.isSame(TODAY, 'd');
      case TaskSince.YESTERDAY:
        const YESTERDAY = REFERENCE.clone()
          .subtract(1, 'day')
          .startOf('day');
        return completedAt.isSameOrAfter(YESTERDAY, 'd');
      case TaskSince.ONE_WEEK:
        const ONE_WEEK = REFERENCE.clone()
          .subtract(7, 'day')
          .startOf('day');
        return completedAt.isSameOrAfter(ONE_WEEK, 'd');
      case TaskSince.TWO_WEEKS:
        const TWO_WEEKS = REFERENCE.clone()
          .subtract(14, 'day')
          .startOf('day');
        return completedAt.isSameOrAfter(TWO_WEEKS, 'd');
      case TaskSince.THREE_WEEKS:
        const THREE_WEEKS = REFERENCE.clone()
          .subtract(21, 'day')
          .startOf('day');
        return completedAt.isSameOrAfter(THREE_WEEKS, 'd');
      default:
        return true;
    }
  }
  return false;
}

interface SimpleProps {
  taskGroups: Array<TaskGroup>;
  onTaskDrop: (task: Task, previousTaskGroupID: string) => void;
  onTaskGroupDrop: (taskGroup: TaskGroup) => void;

  onTaskClick: (task: Task) => void;
  onCreateTask: (taskGroupID: string, name: string) => void;
  onChangeTaskGroupName: (taskGroupID: string, name: string) => void;
  onQuickEditorOpen: ($target: React.RefObject<HTMLElement>, taskID: string, taskGroupID: string) => void;
  onCreateTaskGroup: (listName: string) => void;
  onExtraMenuOpen: (taskGroupID: string, $targetRef: React.RefObject<HTMLElement>) => void;
  onCardMemberClick: OnCardMemberClick;
  onCardLabelClick: () => void;
  cardLabelVariant: CardLabelVariant;
  isPublic?: boolean;
  taskStatusFilter?: TaskStatusFilter;
  taskMetaFilters?: TaskMetaFilters;
  taskSorting?: TaskSorting;
}

const initTaskStatusFilter: TaskStatusFilter = {
  status: TaskStatus.ALL,
  since: TaskSince.ALL,
};

const initTaskMetaFilters: TaskMetaFilters = {
  match: TaskMetaMatch.MATCH_ANY,
  dueDate: null,
  taskName: null,
  labels: [],
  members: [],
};

const initTaskSorting: TaskSorting = {
  type: TaskSortingType.NONE,
  direction: TaskSortingDirection.ASC,
};

const SimpleLists: React.FC<SimpleProps> = ({
  taskGroups,
  onTaskDrop,
  onChangeTaskGroupName,
  onCardLabelClick,
  onTaskGroupDrop,
  onTaskClick,
  onCreateTask,
  onQuickEditorOpen,
  onCreateTaskGroup,
  cardLabelVariant,
  onExtraMenuOpen,
  onCardMemberClick,
  taskStatusFilter = initTaskStatusFilter,
  isPublic = false,
  taskMetaFilters = initTaskMetaFilters,
  taskSorting = initTaskSorting,
}) => {
  const onDragEnd = ({ draggableId, source, destination, type }: DropResult) => {
    if (typeof destination === 'undefined') return;
    if (!isPositionChanged(source, destination)) return;

    const isList = type === 'column';
    const isSameList = destination.droppableId === source.droppableId;
    let droppedDraggable: DraggableElement | null = null;
    let beforeDropDraggables: Array<DraggableElement> | null = null;

    if (isList) {
      const droppedGroup = taskGroups.find(taskGroup => taskGroup.id === draggableId);
      if (droppedGroup) {
        droppedDraggable = {
          id: draggableId,
          position: droppedGroup.position,
        };
        beforeDropDraggables = getSortedDraggables(
          taskGroups.map(taskGroup => {
            return { id: taskGroup.id, position: taskGroup.position };
          }),
        );
        if (droppedDraggable === null || beforeDropDraggables === null) {
          throw new Error('before drop draggables is null');
        }
        const afterDropDraggables = getAfterDropDraggableList(
          beforeDropDraggables,
          droppedDraggable,
          isList,
          isSameList,
          destination,
        );
        const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
        onTaskGroupDrop({
          ...droppedGroup,
          position: newPosition,
        });
      } else {
        throw new Error('task group can not be found');
      }
    } else {
      const curTaskGroup = taskGroups.findIndex(
        taskGroup => taskGroup.tasks.findIndex(task => task.id === draggableId) !== -1,
      );
      let targetTaskGroup = curTaskGroup;
      if (!isSameList) {
        targetTaskGroup = taskGroups.findIndex(taskGroup => taskGroup.id === destination.droppableId);
      }
      const droppedTask = taskGroups[curTaskGroup].tasks.find(task => task.id === draggableId);

      if (droppedTask) {
        droppedDraggable = {
          id: draggableId,
          position: droppedTask.position,
        };
        beforeDropDraggables = getSortedDraggables(
          taskGroups[targetTaskGroup].tasks.map(task => {
            return { id: task.id, position: task.position };
          }),
        );
        if (droppedDraggable === null || beforeDropDraggables === null) {
          throw new Error('before drop draggables is null');
        }
        const afterDropDraggables = getAfterDropDraggableList(
          beforeDropDraggables,
          droppedDraggable,
          isList,
          isSameList,
          destination,
        );
        const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
        const newTask = {
          ...droppedTask,
          position: newPosition,
          taskGroup: {
            id: destination.droppableId,
          },
        };
        onTaskDrop(newTask, droppedTask.taskGroup.id);
      }
    }
  };

  const [currentComposer, setCurrentComposer] = useState('');
  const [toggleLabels, setToggleLabels] = useState(false);
  const [toggleDirection, setToggleDirection] = useState<'shrink' | 'expand'>(
    cardLabelVariant === 'large' ? 'shrink' : 'expand',
  );

  return (
    <BoardContainer>
      <BoardWrapper>
        <DragDropContext onDragEnd={onDragEnd}>
          <Droppable direction="horizontal" type="column" droppableId="root">
            {provided => (
              <Container {...provided.droppableProps} ref={provided.innerRef}>
                {taskGroups
                  .slice()
                  .sort((a: any, b: any) => a.position - b.position)
                  .map((taskGroup: TaskGroup, index: number) => {
                    return (
                      <Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
                        {columnDragProvided => (
                          <Droppable type="tasks" droppableId={taskGroup.id}>
                            {(columnDropProvided, snapshot) => (
                              <List
                                name={taskGroup.name}
                                onOpenComposer={id => setCurrentComposer(id)}
                                isComposerOpen={currentComposer === taskGroup.id}
                                onSaveName={name => onChangeTaskGroupName(taskGroup.id, name)}
                                isPublic={isPublic}
                                ref={columnDragProvided.innerRef}
                                wrapperProps={columnDragProvided.draggableProps}
                                headerProps={columnDragProvided.dragHandleProps}
                                onExtraMenuOpen={onExtraMenuOpen}
                                id={taskGroup.id}
                                key={taskGroup.id}
                                index={index}
                              >
                                <ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
                                  {taskGroup.tasks
                                    .slice()
                                    .filter(t => shouldStatusFilter(t, taskStatusFilter))
                                    .filter(t => shouldMetaFilter(t, taskMetaFilters))
                                    .sort((a: any, b: any) => a.position - b.position)
                                    .sort((a: any, b: any) => sortTasks(a, b, taskSorting))
                                    .map((task: Task, taskIndex: any) => {
                                      return (
                                        <Draggable
                                          key={task.id}
                                          draggableId={task.id}
                                          index={taskIndex}
                                          isDragDisabled={taskSorting.type !== TaskSortingType.NONE}
                                        >
                                          {taskProvided => {
                                            return (
                                              <Card
                                                toggleDirection={toggleDirection}
                                                toggleLabels={toggleLabels}
                                                isPublic={isPublic}
                                                labelVariant={cardLabelVariant}
                                                wrapperProps={{
                                                  ...taskProvided.draggableProps,
                                                  ...taskProvided.dragHandleProps,
                                                }}
                                                setToggleLabels={setToggleLabels}
                                                onCardLabelClick={() => {
                                                  setToggleLabels(true);
                                                  setToggleDirection(
                                                    cardLabelVariant === 'large' ? 'shrink' : 'expand',
                                                  );
                                                  if (onCardLabelClick) {
                                                    onCardLabelClick();
                                                  }
                                                }}
                                                ref={taskProvided.innerRef}
                                                taskID={task.id}
                                                complete={task.complete ?? false}
                                                taskGroupID={taskGroup.id}
                                                description=""
                                                labels={task.labels.map(label => label.projectLabel)}
                                                dueDate={
                                                  task.dueDate
                                                    ? {
                                                        isPastDue: false,
                                                        formattedDate: dayjs(task.dueDate).format('MMM D, YYYY'),
                                                      }
                                                    : undefined
                                                }
                                                title={task.name}
                                                members={task.assigned}
                                                onClick={() => {
                                                  onTaskClick(task);
                                                }}
                                                checklists={task.badges && task.badges.checklist}
                                                onCardMemberClick={onCardMemberClick}
                                                onContextMenu={onQuickEditorOpen}
                                              />
                                            );
                                          }}
                                        </Draggable>
                                      );
                                    })}
                                  {columnDropProvided.placeholder}
                                  {currentComposer === taskGroup.id && (
                                    <CardComposer
                                      onClose={() => {
                                        setCurrentComposer('');
                                      }}
                                      onCreateCard={name => {
                                        onCreateTask(taskGroup.id, name);
                                      }}
                                      isOpen
                                    />
                                  )}
                                </ListCards>
                              </List>
                            )}
                          </Droppable>
                        )}
                      </Draggable>
                    );
                  })}
                {provided.placeholder}
              </Container>
            )}
          </Droppable>
        </DragDropContext>
        {!isPublic && (
          <AddList
            onSave={listName => {
              onCreateTaskGroup(listName);
            }}
          />
        )}
      </BoardWrapper>
    </BoardContainer>
  );
};

export default SimpleLists;
