import React from 'react';
import { DAYS_IN_WEEK, MILLISECONDS_IN_ONE_DAY, DAY_LABELS, MONTH_LABELS } from './constants.js';
import {
    shiftDate,
    getBeginningTimeForDate,
    convertToDate,
    getRange,
} from "../../utils";
import {CalendarHeatmapProp} from "./CalendarHeatmapProp";

const SQUARE_SIZE = 10;
const MONTH_LABEL_GUTTER_SIZE = 4;
const CSS_PSEUDO_NAMESPACE = 'react-calendar-heatmap-';

function CalendarHeatmap({
    values, startDate, endDate, gutterSize, horizontal,
    showMonthLabels, showWeekdayLabels, showOutOfRangeDays, tooltipDataAttrs,
    onClick, onMouseOver, onMouseLeave, transformDayElement
     }: CalendarHeatmapProp) {

    const cachedValues = {};
    for (const i in values) {
        const value = values[i];
        const date = convertToDate(value.date);
        const index = Math.floor((date.getTime() - getStartDateWithEmptyDays().getTime()) / MILLISECONDS_IN_ONE_DAY);
        cachedValues[index] = {
            date: value.date,
            event: value.event,
            className: classForValue(value.id),
            title: titleForValue(value.id),
            tooltipDataAttrs: getTooltipDataAttrsForValue(value.id),
        };
    }

    function getDateDifferenceInDays() {
        const timeDiff = getEndDate().getTime() - convertToDate(startDate).getTime();
        return Math.ceil(timeDiff / MILLISECONDS_IN_ONE_DAY);
    }

    function classForValue(value) {
        return value ? 'color-filled' : 'color-empty';
    }

    function titleForValue(value) {
        return value;
    }

    function getSquareSizeWithGutter() {
        return SQUARE_SIZE + gutterSize;
    }

    function getMonthLabelSize() {
        if (!showMonthLabels) {
            return 0;
        }
        if (horizontal) {
            return SQUARE_SIZE + MONTH_LABEL_GUTTER_SIZE;
        }
        return 2 * (SQUARE_SIZE + MONTH_LABEL_GUTTER_SIZE);
    }

    function getWeekdayLabelSize() {
        if (!showWeekdayLabels) {
            return 0;
        }
        if (horizontal) {
            return 30;
        }
        return SQUARE_SIZE * 1.5;
    }

    function getStartDate() {
        return shiftDate(getEndDate(), -getDateDifferenceInDays() + 1); // +1 because endDate is inclusive
    }

    function getEndDate(): Date {
        return getBeginningTimeForDate(convertToDate(endDate));
    }

    function getStartDateWithEmptyDays() {
        return shiftDate(getStartDate(), -getNumEmptyDaysAtStart());
    }

    function getNumEmptyDaysAtStart() {
        return getStartDate().getDay();
    }

    function getNumEmptyDaysAtEnd() {
        return DAYS_IN_WEEK - 1 - getEndDate().getDay();
    }

    function getWeekCount() {
        const numDaysRoundedToWeek =
            getDateDifferenceInDays() + getNumEmptyDaysAtStart() + getNumEmptyDaysAtEnd();
        return Math.ceil(numDaysRoundedToWeek / DAYS_IN_WEEK);
    }

    function getWeekWidth() {
        return DAYS_IN_WEEK * getSquareSizeWithGutter();
    }

    function getWidth() {
        return (
            getWeekCount() * getSquareSizeWithGutter() -
            (gutterSize - getWeekdayLabelSize())
        );
    }

    function getHeight() {
        return (
            getWeekWidth() +
            (getMonthLabelSize() - gutterSize) +
            getWeekdayLabelSize()
        );
    }

    function getValueForIndex(index) {
        if (cachedValues[index]) {
            console.log(index, cachedValues);
            return cachedValues[index].event;
        }
        return null;
    }

    function getClassNameForIndex(index) {
        if (cachedValues[index]) {
            return cachedValues[index].className;
        }
        return classForValue(null);
    }

    function getTitleForIndex(index) {
        if (cachedValues[index]) {
            return cachedValues[index].title;
        }
        return titleForValue ? titleForValue(null) : null;
    }

    function getTooltipDataAttrsForIndex(index) {
        if (cachedValues[index]) {
            return cachedValues[index].tooltipDataAttrs;
        }
        return getTooltipDataAttrsForValue({ date: null, count: null });
    }

    function getTooltipDataAttrsForValue(value) {
        if (typeof tooltipDataAttrs === 'function') {
            return tooltipDataAttrs(value);
        }
        return tooltipDataAttrs;
    }

    function getTransformForWeek(weekIndex) {
        if (horizontal) {
            return `translate(${weekIndex * getSquareSizeWithGutter()}, 0)`;
        }
        return `translate(0, ${weekIndex * getSquareSizeWithGutter()})`;
    }

    function getTransformForWeekdayLabels() {
        if (horizontal) {
            return `translate(${SQUARE_SIZE}, ${getMonthLabelSize()})`;
        }
        return "";
    }

    function getTransformForMonthLabels() {
        if (horizontal) {
            return `translate(${getWeekdayLabelSize()}, 0)`;
        }
        return `translate(${getWeekWidth() + MONTH_LABEL_GUTTER_SIZE}, ${getWeekdayLabelSize()})`;
    }

    function getTransformForAllWeeks() {
        if (horizontal) {
            return `translate(${getWeekdayLabelSize()}, ${getMonthLabelSize()})`;
        }
        return `translate(0, ${getWeekdayLabelSize()})`;
    }

    function getViewBox() {
        if (horizontal) {
            return `0 0 ${getWidth()} ${getHeight()}`;
        }
        return `0 0 ${getHeight()} ${getWidth()}`;
    }

    function getSquareCoordinates(dayIndex) {
        if (horizontal) {
            return [0, dayIndex * getSquareSizeWithGutter()];
        }
        return [dayIndex * getSquareSizeWithGutter(), 0];
    }

    function getWeekdayLabelCoordinates(dayIndex) {
        if (horizontal) {
            return [0, (dayIndex + 1) * SQUARE_SIZE + dayIndex * gutterSize];
        }
        return [dayIndex * SQUARE_SIZE + dayIndex * gutterSize, SQUARE_SIZE];
    }

    function getMonthLabelCoordinates(weekIndex) {
        if (horizontal) {
            return [
                weekIndex * getSquareSizeWithGutter(),
                getMonthLabelSize() - MONTH_LABEL_GUTTER_SIZE,
            ];
        }
        const verticalOffset = -2;
        return [0, (weekIndex + 1) * getSquareSizeWithGutter() + verticalOffset];
    }

    function handleClick(value) {
        if (onClick) {
            onClick(value);
        }
    }

    function handleMouseOver(e, value) {
        if (onMouseOver) {
            onMouseOver(e, value);
        }
    }

    function handleMouseLeave(e, value) {
        if (onMouseLeave) {
            onMouseLeave(e, value);
        }
    }

    function renderSquare(dayIndex, index) {
        const indexOutOfRange =
            index < getNumEmptyDaysAtStart() ||
            index >= getNumEmptyDaysAtStart() + getDateDifferenceInDays();
        if (indexOutOfRange && !showOutOfRangeDays) {
            return null;
        }
        const [x, y] = getSquareCoordinates(dayIndex);
        const value = getValueForIndex(index);
        const rect = (
            <rect
                key={index}
                width={SQUARE_SIZE}
                height={SQUARE_SIZE}
                x={x}
                y={y}
                className={getClassNameForIndex(index)}
                onClick={() => handleClick(value)}
                onMouseOver={(e) => handleMouseOver(e, value)}
                onMouseLeave={(e) => handleMouseLeave(e, value)}
                {...getTooltipDataAttrsForIndex(index)}
            >
                <title>{getTitleForIndex(index)}</title>
            </rect>
        );
        return transformDayElement ? transformDayElement(rect, value as string, index) : rect;
    }

    function renderWeek(weekIndex) {
        return (
            <g
                key={weekIndex}
                transform={getTransformForWeek(weekIndex)}
                className={`${CSS_PSEUDO_NAMESPACE}week`}
            >
                {getRange(DAYS_IN_WEEK).map((dayIndex) =>
                    renderSquare(dayIndex, weekIndex * DAYS_IN_WEEK + dayIndex),
                )}
            </g>
        );
    }

    function renderAllWeeks() {
        return getRange(getWeekCount()).map((weekIndex) => renderWeek(weekIndex));
    }

    function renderMonthLabels() {
        if (!showMonthLabels) {
            return null;
        }
        const weekRange = getRange(getWeekCount() - 1); // don't render for last week, because label will be cut off
        return weekRange.map((weekIndex) => {
            const endOfWeek = shiftDate(getStartDateWithEmptyDays(), (weekIndex + 1) * DAYS_IN_WEEK);
            const [x, y] = getMonthLabelCoordinates(weekIndex);
            return endOfWeek.getDate() >= 1 && endOfWeek.getDate() <= DAYS_IN_WEEK ? (
                <text key={weekIndex} x={x} y={y} className={`${CSS_PSEUDO_NAMESPACE}month-label`}>
            {MONTH_LABELS[endOfWeek.getMonth()]}
            </text>
        ) : null;
        });
    }

    function renderWeekdayLabels() {
        if (!showWeekdayLabels) {
            return null;
        }
        return DAY_LABELS.map((weekdayLabel, dayIndex) => {
            const [x, y] = getWeekdayLabelCoordinates(dayIndex);
            const cssClasses = `${
                horizontal ? '' : `${CSS_PSEUDO_NAMESPACE}small-text`
            } ${CSS_PSEUDO_NAMESPACE}weekday-label`;
            // eslint-disable-next-line no-bitwise
            return dayIndex & 1 ? (
                <text key={`${x}${y}`} x={x} y={y} className={cssClasses}>
                {weekdayLabel}
                </text>
        ) : null;
        });
    }

    return (
        <svg className="react-calendar-heatmap" viewBox={getViewBox()}>
            <g
                transform={getTransformForMonthLabels()}
                className={`${CSS_PSEUDO_NAMESPACE}month-labels`}
            >
                {renderMonthLabels()}
            </g>
            <g
            transform={getTransformForAllWeeks()}
            className={`${CSS_PSEUDO_NAMESPACE}all-weeks`}
        >
            {renderAllWeeks()}
            </g>
            <g
            transform={getTransformForWeekdayLabels()}
            className={`${CSS_PSEUDO_NAMESPACE}weekday-labels`}
        >
            {renderWeekdayLabels()}
            </g>
        </svg>
    );
}

export default CalendarHeatmap;
