import {
    CHEST_LOG_FILTER_ALL,
    CHEST_LOG_FILTER_CURRENCY,
    CHEST_LOG_FILTER_FURNI,
    CHEST_TRANSACTION_SOURCE_WIRED,
    CHEST_TRANSACTION_WITHDRAW,
    FurnitureType,
    IWiredChestTransactionRow,
    RoomControllerLevel,
    WiredChestLockComposer,
    WiredChestRoomLogsComposer,
    WiredChestRoomLogsEvent,
    WiredChestTransactionDetailsComposer,
    WiredChestTransactionDetailsEvent,
} from '@octane/renderer';
import { FC, Fragment, JSX, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { localizeWithFallback, ProductImageUtility, SendMessageComposer } from '../../api';
import { Button, OctaneCardContentView, OctaneCardHeaderView, OctaneCardView, Text } from '../../common';
import { useMessageEvent, useNotification, useRoom } from '../../hooks';
import { Select } from '../../common/Select';
import { BaseButton } from '../../common/BaseButton';
import { Hint } from '../../common/Tooltip';
import { NativeTextTree } from '../../common/native-text/NativeTextTree';

/** Rows per request. The preview and the detail window share one page size, so one response shape
 * serves both and there is never a question of which request a page belongs to. */
const PAGE_SIZE = 10;

/** Matches the official tab: the preview refreshes on its own while it is the visible tab. */
const POLL_MS = 20000;

/** The official tab greys the lock buttons briefly after a click so a double click cannot fire twice. */
const LOCK_COOLDOWN_MS = 500;

interface TransactionDetail {
    items: { spriteId: number; quantity: number }[];
}

const FILTERS = [
    { value: CHEST_LOG_FILTER_ALL, key: 'wiredmenu.chests.room_logs.furni_and_coins', fallback: 'Furni and coins' },
    { value: CHEST_LOG_FILTER_CURRENCY, key: 'wiredmenu.chests.room_logs.only_coins', fallback: 'Only coins' },
    { value: CHEST_LOG_FILTER_FURNI, key: 'wiredmenu.chests.room_logs.only_furni', fallback: 'Only furni' },
];

const formatTimestamp = (seconds: number): string => (seconds > 0 ? new Date(seconds * 1000).toLocaleString() : '-');

export const WiredChestsTabView: FC<{}> = () => {
    const { roomSession = null } = useRoom();
    const { showConfirm = null } = useNotification();

    const [filter, setFilter] = useState(CHEST_LOG_FILTER_ALL);
    const [rows, setRows] = useState<IWiredChestTransactionRow[]>([]);
    const [page, setPage] = useState(1);
    const [pageCount, setPageCount] = useState(1);
    const [totalRows, setTotalRows] = useState(0);
    const [loaded, setLoaded] = useState(false);

    const [detailsOpen, setDetailsOpen] = useState(false);
    const [expandedId, setExpandedId] = useState(0);
    const [details, setDetails] = useState<Record<number, TransactionDetail>>({});

    const [lockBusy, setLockBusy] = useState(false);
    /** Zero means the whole room. A room with a dozen chests makes a log nobody can read. */
    const [onlyChestId, setOnlyChestId] = useState(0);

    // The detail window drives paging; while it is open the preview must not poll page 1 out from
    // under it. Held in a ref so the interval effect does not restart on every open/close.
    const detailsOpenRef = useRef(false);
    detailsOpenRef.current = detailsOpen;

    const canManageOwn = !!roomSession && (roomSession.isRoomOwner || roomSession.controllerLevel >= RoomControllerLevel.GUEST);
    const canManageAll = !!roomSession?.isRoomOwner;

    const requestPage = useCallback(
        (nextPage: number, nextFilter: number, chestId = onlyChestId) => {
            SendMessageComposer(new WiredChestRoomLogsComposer(PAGE_SIZE, nextPage, nextFilter, chestId));
        },
        [onlyChestId],
    );

    useEffect(() => {
        requestPage(1, filter);
    }, [filter, requestPage]);

    useEffect(() => {
        const handle = window.setInterval(() => {
            if (detailsOpenRef.current) return;

            requestPage(1, filter);
        }, POLL_MS);

        return () => window.clearInterval(handle);
    }, [filter, requestPage]);

    useMessageEvent<WiredChestRoomLogsEvent>(WiredChestRoomLogsEvent, (event) => {
        const parser = event.getParser();
        if (!parser) return;

        setRows(parser.rows);
        setPage(parser.page);
        setPageCount(parser.pageCount);
        setTotalRows(parser.totalRows);
        setFilter(parser.filter);
        setLoaded(true);
    });

    useMessageEvent<WiredChestTransactionDetailsEvent>(WiredChestTransactionDetailsEvent, (event) => {
        const parser = event.getParser();
        if (!parser) return;

        setDetails((prev) => ({ ...prev, [parser.transactionId]: { items: parser.items } }));
    });

    const sendLock = useCallback(
        (lock: boolean, all: boolean) => {
            if (lockBusy) return;

            // AIR WiredMenuChestsTab.startLockTimeout: the lock gets no answer, so the buttons grey for a short
            // time-out and every chest shows its new state through its furni data.
            setLockBusy(true);
            SendMessageComposer(new WiredChestLockComposer(lock, all));
            window.setTimeout(() => setLockBusy(false), LOCK_COOLDOWN_MS);
        },
        [lockBusy],
    );

    const lockEveryChest = useCallback(() => {
        if (!canManageAll || !showConfirm) return;

        showConfirm(
            localizeWithFallback(
                'wiredmenu.chests.chest_control.lock_all.warning.desc',
                'This locks every chest in the room, including the ones other people own. Wired keeps working - only what people do by hand stops.',
            ),
            () => sendLock(true, true),
            () => {
                // nothing to undo: the confirm was declined before anything was sent
            },
            null,
            null,
            localizeWithFallback('wiredmenu.chests.chest_control.lock_all.warning.title', 'Lock every chest'),
        );
    }, [canManageAll, sendLock, showConfirm]);

    const toggleDetail = useCallback(
        (transactionId: number, hasDetails: boolean) => {
            if (expandedId === transactionId) {
                setExpandedId(0);
                return;
            }

            setExpandedId(transactionId);
            if (hasDetails && !details[transactionId]) SendMessageComposer(new WiredChestTransactionDetailsComposer(transactionId));
        },
        [details, expandedId],
    );

    const closeDetails = useCallback(() => {
        setDetailsOpen(false);
        setExpandedId(0);
        requestPage(1, filter);
    }, [filter, requestPage]);

    const previewRows = useMemo(() => (page === 1 ? rows : []), [page, rows]);

    const typeLabel = (row: IWiredChestTransactionRow) =>
        row.type === CHEST_TRANSACTION_WITHDRAW
            ? localizeWithFallback('wiredchests.logs.type.withdraw', 'Withdrawal')
            : localizeWithFallback('wiredchests.logs.type.deposit', 'Deposit');

    const sourceLabel = (row: IWiredChestTransactionRow) =>
        row.source === CHEST_TRANSACTION_SOURCE_WIRED
            ? localizeWithFallback('wiredchests.logs.source.wired', 'Wired')
            : localizeWithFallback('wiredchests.logs.source.user', 'Player');

    const renderPreviewBody = () => {
        if (!loaded) return null;

        if (!previewRows.length) {
            return (
                <NativeTextTree>
                    <div className="p-3 text-[12px] text-[#6b6659]">
                        {localizeWithFallback('wiredmenu.chests.room_logs.empty', 'No transactions yet.')}
                    </div>
                </NativeTextTree>
            );
        }

        return (
            <NativeTextTree>
                <table className="w-full text-[12px]">
                    <thead className="bg-[#efede5] sticky top-0">
                        <tr>
                            <th className="text-left px-2 py-1">{localizeWithFallback('wiredmenu.chests.room_logs.column.type', 'Type')}</th>
                            <th className="text-left px-2 py-1">
                                {localizeWithFallback('wiredmenu.chests.room_logs.column.username', 'Username')}
                            </th>
                            <th className="text-right px-2 py-1">
                                {localizeWithFallback('wiredmenu.chests.room_logs.column.withdraws', 'Withdrawals')}
                            </th>
                            <th className="text-right px-2 py-1">
                                {localizeWithFallback('wiredmenu.chests.room_logs.column.deposits', 'Deposits')}
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        {previewRows.map((row) => (
                            <tr key={row.transactionId} className="border-t border-[#e4e0d5]">
                                <td className="px-2 py-1">{typeLabel(row)}</td>
                                <td className="px-2 py-1">{row.userName || sourceLabel(row)}</td>
                                <td className="px-2 py-1 text-right tabular-nums">{row.withdrawn || '-'}</td>
                                <td className="px-2 py-1 text-right tabular-nums">{row.deposited || '-'}</td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </NativeTextTree>
        );
    };

    const renderDetailItems = (transactionId: number, hasDetails: boolean) => {
        const detail = details[transactionId];

        if (!hasDetails || (detail && !detail.items.length)) {
            return (
                <NativeTextTree>
                    <div className="text-[12px] text-[#6b6659]">
                        {localizeWithFallback('wiredchests.log_details.incomplete_data', 'No furni recorded for this transaction.')}
                    </div>
                </NativeTextTree>
            );
        }

        if (!detail) return <div className="text-[12px] text-[#6b6659]">...</div>;

        return (
            <div className="flex flex-wrap gap-2">
                {detail.items.map((item) => (
                    <div key={item.spriteId} className="flex items-center gap-1 border border-[#d1ccbf] rounded bg-white px-1 py-[2px]">
                        <img
                            alt=""
                            className="max-h-[28px] max-w-[28px] object-contain"
                            // The server negates a wall item's sprite id: the two kinds have their own id ranges.
                            src={ProductImageUtility.getProductImageUrl(
                                item.spriteId < 0 ? FurnitureType.WALL : FurnitureType.FLOOR,
                                Math.abs(item.spriteId),
                                '',
                            )}
                        />
                        <span className="text-[12px] tabular-nums">x{item.quantity}</span>
                    </div>
                ))}
            </div>
        );
    };

    return (
        <NativeTextTree>
            <>
                <div className="p-3 flex flex-col gap-3">
                    <div className="bg-white rounded border border-[#b9b3a5] p-2 flex flex-col gap-2">
                        <div className="flex items-center justify-between gap-2">
                            <Text bold>{localizeWithFallback('wiredmenu.chests.room_logs.title', 'Room transaction log')}</Text>
                            <Select
                                className="rounded border border-[#7f7f7f] bg-[#ece9e1] px-2 py-[2px] text-[12px] text-[#333]"
                                value={filter}
                                onChange={(event) => setFilter(parseInt(event.target.value, 10))}
                            >
                                {FILTERS.map((option) => (
                                    <option key={option.value} value={option.value}>
                                        {localizeWithFallback(option.key, option.fallback)}
                                    </option>
                                ))}
                            </Select>
                        </div>
                        {!!onlyChestId && (
                            <div className="flex items-center justify-between gap-2 rounded border border-[#c08a5a] bg-[#f7e6cf] px-2 py-1">
                                <span className="text-[12px] text-[#7a4a1c]">
                                    {localizeWithFallback(
                                        'wiredmenu.chests.room_logs.filtered_to_chest',
                                        'Showing chest %id% only',
                                        ['id'],
                                        [String(onlyChestId)],
                                    )}
                                </span>
                                <Button variant="secondary" onClick={() => setOnlyChestId(0)}>
                                    {localizeWithFallback('wiredmenu.chests.room_logs.show_all_chests', 'All chests')}
                                </Button>
                            </div>
                        )}
                        <div className="max-h-[180px] overflow-y-auto border border-[#d1ccbf] rounded">{renderPreviewBody()}</div>
                        <div className="flex items-center justify-between gap-2">
                            <span className="text-[12px] text-[#6b6659] tabular-nums">
                                {localizeWithFallback('wiredmenu.chests.room_logs.total', '%count% transactions', ['count'], [String(totalRows)])}
                            </span>
                            <Button disabled={!totalRows} variant="secondary" onClick={() => setDetailsOpen(true)}>
                                {localizeWithFallback('wiredmenu.chests.room_logs.view_in_detail', 'View in detail')}
                            </Button>
                        </div>
                    </div>

                    <div className="bg-white rounded border border-[#b9b3a5] p-2 flex flex-col gap-2">
                        <Text bold>{localizeWithFallback('wiredmenu.chests.chest_control.title', 'Chest control')}</Text>
                        <div className="text-[12px] text-[#6b6659]">
                            {localizeWithFallback(
                                'wiredmenu.chests.chest_control.hint',
                                'A locked chest still answers wired - only what people do by hand is blocked, in both directions.',
                            )}
                        </div>
                        <div className="flex flex-wrap gap-2">
                            <Button disabled={!canManageOwn || lockBusy} variant="secondary" onClick={() => sendLock(true, false)}>
                                {localizeWithFallback('wiredmenu.chests.chest_control.lock_own', 'Lock my chests')}
                            </Button>
                            <Button disabled={!canManageOwn || lockBusy} variant="secondary" onClick={() => sendLock(false, false)}>
                                {localizeWithFallback('wiredmenu.chests.chest_control.unlock_own', 'Unlock my chests')}
                            </Button>
                            <Button disabled={!canManageAll || lockBusy} variant="danger" onClick={lockEveryChest}>
                                {localizeWithFallback('wiredmenu.chests.chest_control.lock_all', 'Lock every chest')}
                            </Button>
                        </div>
                        {!canManageAll && (
                            <div className="text-[12px] text-[#8c877d]">
                                {localizeWithFallback('wiredmenu.chests.chest_control.owner_only', 'Only the room owner can lock every chest.')}
                            </div>
                        )}
                    </div>
                </div>

                {detailsOpen && (
                    <WiredChestTransactionsWindow
                        expandedId={expandedId}
                        page={page}
                        pageCount={pageCount}
                        renderDetailItems={renderDetailItems}
                        rows={rows}
                        sourceLabel={sourceLabel}
                        typeLabel={typeLabel}
                        onClose={closeDetails}
                        onFilterChest={setOnlyChestId}
                        onPage={(nextPage) => requestPage(nextPage, filter)}
                        onToggleDetail={toggleDetail}
                    />
                )}
            </>
        </NativeTextTree>
    );
};

interface WiredChestTransactionsWindowProps {
    rows: IWiredChestTransactionRow[];
    page: number;
    pageCount: number;
    expandedId: number;
    typeLabel: (row: IWiredChestTransactionRow) => string;
    sourceLabel: (row: IWiredChestTransactionRow) => string;
    renderDetailItems: (transactionId: number, hasDetails: boolean) => JSX.Element;
    onClose: () => void;
    onPage: (page: number) => void;
    onToggleDetail: (transactionId: number, hasDetails: boolean) => void;
    /** Narrow the whole log to one chest. A room full of chests logs more than anyone can read. */
    onFilterChest: (chestId: number) => void;
}

/**
 * The "view in detail" window: the same log, paged, with the chest and timestamp columns the preview
 * has no room for and a per-row breakdown of the furni that moved.
 */
const WiredChestTransactionsWindow: FC<WiredChestTransactionsWindowProps> = (props) => {
    const {
        rows,
        page,
        pageCount,
        expandedId,
        typeLabel,
        sourceLabel,
        renderDetailItems,
        onClose,
        onPage,
        onToggleDetail,
        onFilterChest,
    } = props;

    return (
        <NativeTextTree>
            <OctaneCardView className="octane-wired-menu min-w-[720px] max-w-[720px]" theme="primary-slim" uniqueKey="wired-chest-transactions">
                <OctaneCardHeaderView
                    headerText={localizeWithFallback('wiredchests.logs.room_title', 'Room transactions')}
                    onCloseClick={onClose}
                />
                <OctaneCardContentView>
                    <div className="flex flex-col gap-2">
                        <div className="max-h-[320px] overflow-y-auto border border-[#d1ccbf] rounded bg-white">
                            <table className="w-full text-[12px]">
                                <thead className="bg-[#efede5] sticky top-0">
                                    <tr>
                                        <th className="text-left px-2 py-1">{localizeWithFallback('wiredchests.logs.col.chests', 'Chest')}</th>
                                        <th className="text-left px-2 py-1">
                                            {localizeWithFallback('wiredchests.logs.col.timestamp', 'Timestamp')}
                                        </th>
                                        <th className="text-left px-2 py-1">{localizeWithFallback('wiredchests.logs.col.type', 'Type')}</th>
                                        <th className="text-left px-2 py-1">
                                            {localizeWithFallback('wiredchests.logs.col.source', 'Source')}
                                        </th>
                                        <th className="text-left px-2 py-1">
                                            {localizeWithFallback('wiredchests.logs.col.username', 'Username')}
                                        </th>
                                        <th className="text-right px-2 py-1">
                                            {localizeWithFallback('wiredchests.logs.col.withdraws', 'Withdrawals')}
                                        </th>
                                        <th className="text-right px-2 py-1">
                                            {localizeWithFallback('wiredchests.logs.col.deposits', 'Deposits')}
                                        </th>
                                        <th className="text-right px-2 py-1">
                                            {localizeWithFallback('wiredchests.logs.col.details', 'Details')}
                                        </th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {rows.map((row) => (
                                        <Fragment key={row.transactionId}>
                                            <tr className="border-t border-[#e4e0d5]">
                                                <td className="px-2 py-1 tabular-nums">
                                                    <Hint content={localizeWithFallback(
                                                            'wiredmenu.chests.room_logs.only_this_chest',
                                                            'Only this chest',
                                                        )}><BaseButton
                                                        type="button"
                                                        className="underline decoration-dotted"
                                                        onClick={() => onFilterChest(row.chestId)}
                                                    >
                                                        {row.chestId}
                                                    </BaseButton></Hint>
                                                </td>
                                                <td className="px-2 py-1">{formatTimestamp(row.timestamp)}</td>
                                                <td className="px-2 py-1">{typeLabel(row)}</td>
                                                <td className="px-2 py-1">{sourceLabel(row)}</td>
                                                <td className="px-2 py-1">{row.userName || '-'}</td>
                                                <td className="px-2 py-1 text-right tabular-nums">{row.withdrawn || '-'}</td>
                                                <td className="px-2 py-1 text-right tabular-nums">{row.deposited || '-'}</td>
                                                <td className="px-2 py-1 text-right">
                                                    <BaseButton
                                                        className="rounded border border-[#7f7f7f] bg-[#ece9e1] px-2 py-[1px] text-[12px] text-[#333] hover:bg-[#e3ded2]"
                                                        type="button"
                                                        onClick={() => onToggleDetail(row.transactionId, row.hasDetails)}
                                                    >
                                                        {expandedId === row.transactionId ? '-' : '+'}
                                                    </BaseButton>
                                                </td>
                                            </tr>
                                            {expandedId === row.transactionId && (
                                                <tr className="bg-[#f7f5ee]">
                                                    <td className="px-2 py-2" colSpan={8}>
                                                        {renderDetailItems(row.transactionId, row.hasDetails)}
                                                    </td>
                                                </tr>
                                            )}
                                        </Fragment>
                                    ))}
                                </tbody>
                            </table>
                        </div>
                        <div className="flex items-center justify-between gap-2">
                            <Button disabled={page <= 1} variant="secondary" onClick={() => onPage(page - 1)}>
                                {localizeWithFallback('wiredchests.logs.previous', 'Previous')}
                            </Button>
                            <span className="text-[12px] text-[#6b6659] tabular-nums">
                                {localizeWithFallback(
                                    'wiredchests.logs.page',
                                    'Page %page% of %pages%',
                                    ['page', 'pages'],
                                    [String(page), String(pageCount)],
                                )}
                            </span>
                            <Button disabled={page >= pageCount} variant="secondary" onClick={() => onPage(page + 1)}>
                                {localizeWithFallback('wiredchests.logs.next', 'Next')}
                            </Button>
                        </div>
                    </div>
                </OctaneCardContentView>
            </OctaneCardView>
        </NativeTextTree>
    );
};
