import type { TransactionFilter } from "@olenbetong/appframe-updater";
import { Box, Text, useApp } from "ink";
import { useCallback, useEffect, useMemo, useState } from "react";
import type { Server } from "../lib/Server.js";
import { TransactionsPreviewDialog } from "./TransactionsPreviewDialog.js";
import { createStatusLine } from "./tableFormatting.js";
import { useCommand } from "./useCommand.js";
import { useScreenSize } from "./useScreenSize.js";
import { useTransactionEdits } from "./useTransactionEdits.js";
import { type TransactionsEditorRefreshOptions, useTransactions } from "./useTransactions.js";
import { useTransactionsSelection } from "./useTransactionsSelection.js";
import { useTransactionsTableLayout } from "./useTransactionsTableLayout.js";
import { CURSOR_PREFIX_WIDTH, HEADER_PREFIX, useVirtualScrolling } from "./useVirtualScrolling.js";
import { withFullScreen } from "./withFullScreen.js";

const HEADER_LINES = 2;
const BASE_FOOTER_LINES = 1;
const STATUS_LINES = 1;
const FRAME_BORDER_LINES = 2;
const SAVE_SHORTCUTS = ["ctrl+s", "meta+s"];
const EXIT_SHORTCUTS = ["ctrl+x", "meta+x"];

export interface TransactionsEditorProps {
	server: Server;
	namespace: string | number;
	showErrorColumn?: boolean;
	filter?: TransactionFilter;
}

export function TransactionsEditor({ filter, server, namespace, showErrorColumn = true }: TransactionsEditorProps) {
	let { width: terminalColumns, height: terminalRows } = useScreenSize();
	let { exit } = useApp();
	let [cursor, setCursor] = useState(0);
	let [actionMessage, setActionMessage] = useState<string | null>(null);
	let [actionColor, setActionColor] = useState("gray");
	let [previewOpen, setPreviewOpen] = useState(false);

	let onActionMessage = useCallback((message: string | null, color: "gray" | "red" | "yellow" | "green") => {
		setActionMessage(message);
		setActionColor(color);
	}, []);

	let {
		transactions,
		loading,
		error,
		refresh: fetchTransactions,
		normalizedTransactions,
		transactionMap,
	} = useTransactions({ server, namespace, filter });

	let [lastRefreshOptions, setLastRefreshOptions] = useState<TransactionsEditorRefreshOptions>({
		preserveViewport: false,
		anchorPrimKey: null,
	});

	let refresh = useCallback(
		async (options: TransactionsEditorRefreshOptions = {}) => {
			let { preserveViewport = false, anchorPrimKey = null } = options;
			setLastRefreshOptions({ preserveViewport, anchorPrimKey });
			await fetchTransactions(options);
		},
		[fetchTransactions],
	);

	let { selectedPrimKeys, toggleSelection } = useTransactionsSelection(transactions);
	let { pendingStatuses, modifiedPrimKeys, cycleStatus, save } = useTransactionEdits({
		normalizedTransactions,
		transactionRecordMap: transactionMap,
		selectedPrimKeys,
		server,
		onActionMessage,
		refresh,
	});

	let displayTransactions = useMemo(
		() =>
			normalizedTransactions.map((transaction) => ({
				...transaction,
				Status: pendingStatuses[transaction.PrimKey] ?? transaction.Status,
			})),
		[normalizedTransactions, pendingStatuses],
	);

	let instructionsLineCount = BASE_FOOTER_LINES;
	let frameHeight = Math.max(
		terminalRows - instructionsLineCount,
		FRAME_BORDER_LINES + HEADER_LINES + STATUS_LINES + 1,
	);
	let innerHeight = Math.max(frameHeight - FRAME_BORDER_LINES, 0);
	let dataAreaHeight = Math.max(innerHeight - HEADER_LINES - STATUS_LINES, 0);
	let frameWidth = Math.max(terminalColumns, FRAME_BORDER_LINES + CURSOR_PREFIX_WIDTH + 1);
	let innerWidth = Math.max(frameWidth - FRAME_BORDER_LINES, 0);
	let tableWidth = Math.max(innerWidth - CURSOR_PREFIX_WIDTH, 0);
	let { columns, rows, widths, tableContentWidth, lineWidth, header, divider } = useTransactionsTableLayout({
		displayTransactions,
		showErrorColumn,
		tableWidth,
	});

	let { bodyLines, pageStep, stats, setOffset } = useVirtualScrolling({
		cursor,
		setCursor,
		dataAreaHeight,
		rows,
		columns,
		widths,
		tableContentWidth,
		lineWidth,
		normalizedTransactions,
		selectedPrimKeys,
		modifiedPrimKeys,
		loading,
	});

	useEffect(() => {
		let { preserveViewport = false, anchorPrimKey = null } = lastRefreshOptions;
		if (!preserveViewport) {
			setOffset(0);
			setCursor(0);
			return;
		}
		if (anchorPrimKey) {
			let index = transactions.findIndex((transaction) => transaction.PrimKey === anchorPrimKey);
			if (index >= 0) {
				setCursor(index);
			}
		}
	}, [lastRefreshOptions, setOffset, transactions]);

	function handlePreview() {
		let focused = normalizedTransactions[cursor];
		if (!focused) {
			onActionMessage("No transaction focused for preview.", "yellow");
			return;
		}

		setPreviewOpen(true);
	}

	let totalRowCount = stats.totalRowCount;

	function focusPrevious() {
		setCursor((current) => (totalRowCount === 0 ? 0 : Math.max(current - 1, 0)));
	}

	function focusNext() {
		setCursor((current) => {
			if (totalRowCount === 0) {
				return 0;
			}
			return Math.min(current + 1, totalRowCount - 1);
		});
	}

	function pageUpCursor() {
		setCursor((current) => Math.max(current - pageStep, 0));
	}

	function pageDownCursor() {
		setCursor((current) => {
			if (totalRowCount === 0) {
				return 0;
			}
			return Math.min(current + pageStep, totalRowCount - 1);
		});
	}

	let focusedPrimKey = normalizedTransactions[cursor]?.PrimKey ?? null;

	function handlePreviewClose() {
		if (previewOpen) {
			setPreviewOpen(false);
			refresh({ preserveViewport: true, anchorPrimKey: normalizedTransactions[cursor]?.PrimKey ?? null });
		}
	}

	let tableCommandsEnabled = !previewOpen;

	useCommand("up", focusPrevious, tableCommandsEnabled);
	useCommand("down", focusNext, tableCommandsEnabled);
	useCommand("pageup", pageUpCursor, tableCommandsEnabled);
	useCommand("pagedown", pageDownCursor, tableCommandsEnabled);
	useCommand("home", () => setCursor(0), tableCommandsEnabled);
	useCommand("end", () => setCursor(Math.max(totalRowCount - 1, 0)), tableCommandsEnabled);
	useCommand(SAVE_SHORTCUTS, save, tableCommandsEnabled);
	useCommand("space", () => toggleSelection(focusedPrimKey), tableCommandsEnabled);
	useCommand("s", () => cycleStatus(focusedPrimKey), tableCommandsEnabled);
	useCommand("r", () => refresh({ preserveViewport: true, anchorPrimKey: focusedPrimKey }), tableCommandsEnabled);
	useCommand("p", handlePreview, tableCommandsEnabled);
	useCommand("escape", handlePreviewClose, true);
	useCommand(EXIT_SHORTCUTS, exit, true);

	let statusParts: string[] = [`showing ${stats.firstVisibleRow}-${stats.lastVisibleRow} of ${stats.totalRowCount}`];

	if (selectedPrimKeys.size > 0) {
		statusParts.push(`${selectedPrimKeys.size} selected`);
	}

	if (modifiedPrimKeys.size > 0) {
		statusParts.push(`${modifiedPrimKeys.size} modified`);
	}

	let statsText = statusParts.length > 0 ? `(${statusParts.join(" - ")})` : "";
	let statusMessage = "";
	let statusColor = "gray";

	if (error) {
		statusMessage = error.message.replace(/\s+/g, " ").trim();
		statusColor = "red";
	} else if (loading) {
		statusMessage = "Loading transactions…";
		statusColor = "blue";
	} else if (actionMessage) {
		statusMessage = actionMessage.replace(/\s+/g, " ").trim();
		statusColor = actionColor;
	}

	let statusLine = createStatusLine(statusMessage, statsText, lineWidth, HEADER_PREFIX);

	return (
		<Box flexDirection="column" width={frameWidth} height={terminalRows} position="relative">
			<Box
				flexDirection="column"
				borderStyle="round"
				width={frameWidth}
				height={frameHeight}
				flexGrow={1}
				flexShrink={0}
			>
				<Text wrap="truncate">{header}</Text>
				<Text wrap="truncate">{divider}</Text>
				{bodyLines.map((line) => (
					<Text key={line.key} inverse={line.highlight} wrap="truncate">
						{line.text}
					</Text>
				))}
				<Text inverse color={statusColor} wrap="truncate">
					{statusLine}
				</Text>
			</Box>
			<Text color="gray" wrap="truncate">
				↑/↓ move · PgUp/PgDn jump · Space toggle select · s cycle status · ctrl+s save · ctrl+x exit · r refresh · p
				preview · esc close preview
			</Text>
			{previewOpen ? (
				<TransactionsPreviewDialog
					server={server}
					namespace={namespace}
					frameWidth={frameWidth}
					focusedPrimKey={focusedPrimKey}
					focusNext={focusNext}
					focusPrevious={focusPrevious}
				/>
			) : null}
		</Box>
	);
}

export async function renderTransactionsEditor({ filter = "edit", ...props }: TransactionsEditorProps) {
	const ink = withFullScreen(<TransactionsEditor {...props} filter={filter} />);
	await ink.start();

	return ink;
}
