"use strict";

const React = require("react");
const classnames = require("classnames");
const asExpression = require("as-expression");

const AutosizeTextarea = require("../autoresize-textarea");

const style = require("./table.css");

const MOUSE_PRIMARY = 0;

function generateColumnTemplate(columnCount) {
	return (new Array(columnCount))
		.fill("auto")
		.join(" ");
}

function StaticCellContents({ dummy, children }) {
	return (
		<div className={classnames(style.cellContents, { [style.dummy]: dummy })}>
			{children}
		</div>
	);
}

function EditableCellContents({ onChange, children }) {
	let ref = React.useRef();

	React.useEffect(() => {
		ref.current.focus();
		ref.current.select();
	}, [ ref ]);

	return (
		<AutosizeTextarea
			ref={ref}
			value={children}
			className={style.editableCell}
			onChange={onChange}
			onMouseDown={(event) => {
				// Workaround for Chromium, which will try to select the underlying cell when attempting to click the editable text
				event.stopPropagation();
			}}
		/>
	);
}

function Cell({ rowIndex, columnIndex, children }) {
	let table = React.useContext(TableContext);

	let isSingleSelection = (
		(table.selection.rowStart != null || table.selection.columnStart != null)
		&& rowIndex === table.selection.rowStart
		&& rowIndex === table.selection.rowEnd
		&& columnIndex === table.selection.columnStart
		&& columnIndex === table.selection.columnEnd
	);

	let isPartOfSelection = (
		(table.selection.rowStart != null || table.selection.columnStart != null)
		&& rowIndex >= table.selection.rowStart
		&& rowIndex <= table.selection.rowEnd
		&& columnIndex >= table.selection.columnStart
		&& columnIndex <= table.selection.columnEnd
	);

	let isLeftEdge = (columnIndex === table.selection.columnStart);
	let isRightEdge = (columnIndex === table.selection.columnEnd);
	let isTopEdge = (rowIndex === table.selection.rowStart);
	let isBottomEdge = (rowIndex === table.selection.rowEnd);

	let isEditable = (table.isEditing && isPartOfSelection);

	let contents = (isEditable)
		? <EditableCellContents
			children={children}
			onChange={(contents) => table.onCellContentsChange(rowIndex, columnIndex, contents)}
		/>
		: <StaticCellContents children={children} />;

	return <div
		className={classnames(style.cell, {
			[style.selectionLeftEdge]: isPartOfSelection && isLeftEdge,
			[style.selectionRightEdge]: isPartOfSelection && isRightEdge,
			[style.selectionTopEdge]: isPartOfSelection && isTopEdge,
			[style.selectionBottomEdge]: isPartOfSelection && isBottomEdge,
			[style.selectionPart]: isPartOfSelection
		})}
		children={contents}
		onMouseDown={(event) => table.onCellDown(rowIndex, columnIndex, event)}
		onMouseUp={(event) => table.onCellUp(rowIndex, columnIndex, event)}
		onMouseEnter={(event) => table.onCellEnter(rowIndex, columnIndex, event)}
		onMouseLeave={(event) => table.onCellLeave(rowIndex, columnIndex, event)}
		onDoubleClick={(event) => table.onCellDoubleClick(rowIndex, columnIndex, event)}
		onKeyDown={(event) => {
			if (event.key === "Enter" && !event.shiftKey) {
				table.onCellCompleteEditing(rowIndex, columnIndex);
			}
		}}
	/>;
}

function Row({ columns, index }) {
	return columns.map((cell, columnIndex) => {
		return <Cell rowIndex={index} columnIndex={columnIndex}>{cell}</Cell>
	});
}

let TableContext = React.createContext();

module.exports = function Table({ sheet }) {
	let [ selectedRowStart, setSelectedRowStart ] = React.useState();
	let [ selectedRowEnd, setSelectedRowEnd ] = React.useState();
	let [ selectedColumnStart, setSelectedColumnStart ] = React.useState();
	let [ selectedColumnEnd, setSelectedColumnEnd ] = React.useState();
	let [ isSelecting, setIsSelecting ] = React.useState(false);
	let [ isEditing, setIsEditing ] = React.useState(false);

	// FIXME: Ensure that in editing mode, there's always just *one* cell selected, including when editing mode is activated by eg. pressing Enter (at least until we have multi-cell editing implemented!)

	React.useEffect(() => {
		function mouseupListener(event) {
			if (event.button === MOUSE_PRIMARY) {
				setIsSelecting(false);
			}
		};

		function mousedownListener(event) {
			if (event.button === MOUSE_PRIMARY) {
				stopEditing();
				deselect();
			}
		}

		document.addEventListener("mouseup", mouseupListener);
		document.addEventListener("mousedown", mousedownListener);

		return function () {
			document.removeEventListener("mouseup", mouseupListener);
			document.removeEventListener("mousedown", mousedownListener);
		}
	}, []);

	function deselect() {
		setSelectedRowStart(null);
		setSelectedRowEnd(null);
		setSelectedColumnStart(null);
		setSelectedColumnEnd(null);
	}

	function select(rowIndex, columnIndex) {
		setSelectedRowStart(rowIndex);
		setSelectedRowEnd(rowIndex);
		setSelectedColumnStart(columnIndex);
		setSelectedColumnEnd(columnIndex);
	}

	function selectCellStart(rowIndex, columnIndex) {
		setSelectedRowStart(rowIndex);
		setSelectedColumnStart(columnIndex);
	}

	// FIXME: Swap when end < start
	function selectCellEnd(rowIndex, columnIndex) {
		setSelectedRowEnd(rowIndex);
		setSelectedColumnEnd(columnIndex);
	}

	function startEditing() {
		setIsEditing(true);
	}

	function stopEditing() {
		setIsEditing(false);
	}

	let flippedSelection = asExpression(() => {
		let [ rowStart, rowEnd ] = (selectedRowStart < selectedRowEnd)
			? [ selectedRowStart, selectedRowEnd ]
			: [ selectedRowEnd, selectedRowStart ];

		let [ columnStart, columnEnd ] = (selectedColumnStart < selectedColumnEnd)
			? [ selectedColumnStart, selectedColumnEnd ]
			: [ selectedColumnEnd, selectedColumnStart ];

		return { rowStart, rowEnd, columnStart, columnEnd };
	});

	// TODO: Also stop selecting on focus blur and document mouseleave? Or is this not necessary?
	let tableAPI = {
		selection: flippedSelection,
		isEditing: isEditing,
		onCellDown: function (rowIndex, columnIndex, event) {
			if (event.button === MOUSE_PRIMARY) {
				stopEditing();
				setIsSelecting(true);
				selectCellStart(rowIndex, columnIndex);
				selectCellEnd(rowIndex, columnIndex);
				event.stopPropagation();
			}
		},
		onCellUp: function (rowIndex, columnIndex, event) {
			if (event.button === MOUSE_PRIMARY) {
				setIsSelecting(false);
				event.stopPropagation();
			}
		},
		onCellEnter: function (rowIndex, columnIndex, event) {
			if (isSelecting) {
				selectCellEnd(rowIndex, columnIndex);
			}
		},
		onCellLeave: function (rowIndex, columnIndex, event) {
			
		},
		onCellDoubleClick: function (rowIndex, columnIndex, event) {
			if (event.button === MOUSE_PRIMARY) {
				startEditing();
				select(rowIndex, columnIndex);
				event.stopPropagation();
			}
		},
		onCellContentsChange: function (rowIndex, columnIndex, contents) {
			// FIXME: Properly run this through a state management cycle?
			sheet.cells[rowIndex][columnIndex] = contents;
		},
		onCellCompleteEditing: function (rowIndex, columnIndex) {
			stopEditing();
		}
	};

	return (
		<div className={style.table} style={{ gridTemplateColumns: generateColumnTemplate(sheet.columnCount) }}>
			<TableContext.Provider value={tableAPI}>
				{sheet.cells.map((columns, rowIndex) => {
					return <Row index={rowIndex} columns={columns} />;
				})}
			</TableContext.Provider>
		</div>
	);
}
