/*
 * -------------------------------------------------------------------------
 * This file is part of the MindStudio project.
 * Copyright (c) 2025 Huawei Technologies Co.,Ltd.
 *
 * MindStudio is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 */
import React, { useEffect, useRef, useState } from 'react';
import { Select, Checkbox, CollapsiblePanel } from '@insight/lib/components';
import { useTranslation } from 'react-i18next';
import { observer } from 'mobx-react';
import { runInAction } from 'mobx';
import type { CheckboxChangeEvent } from 'antd/lib/checkbox';
import MemorySliceChart from './MemorySliceChart';
import MemoryFunctionCall from './MemoryFunctionCall';
import { Label } from './Common';
import { getFuncNewData, getBarNewData, getBlockTableData, getEventTableData, getPotentialLeakStats } from './dataHandler';
import { convertNanoseconds } from '../utils/utils';
import { MemoryBlockDiagram } from './leaks/MemoryBlockDiagram';
import { getInitialZoomDomain } from './leaks/zoomDomain';
import MemoryDataZoom from './MemoryDataZoom';
import { workerTransform } from '@/leaksWorker/blockWorker/worker';
import { MemoryStateDiagram } from './leaks/MemoryStateDiagram';
import PotentialLeakStats from './PotentialLeakStats';
import { debounce, type DebouncedFunc } from 'lodash';

type TransformChangeSource = 'wheel' | 'keyboard' | 'drag';
const isValidRange = (range: [number, number]): boolean => Number.isFinite(range[0]) && Number.isFinite(range[1]) && range[0] < range[1];
const isSameRange = (prev: [number, number] | undefined, next: [number, number]): boolean => {
    if (prev === undefined) {
        return false;
    }
    return prev[0] === next[0] && prev[1] === next[1];
};
const clampRangeToBounds = (range: [number, number], min: number, max: number): [number, number] => {
    const totalRange = max - min;
    const rangeWidth = Math.min(range[1] - range[0], totalRange);
    let start = Math.max(min, Math.min(max - rangeWidth, range[0]));
    if (!Number.isFinite(start)) {
        start = min;
    }
    return [Math.floor(start), Math.floor(start + rangeWidth)];
};

const MemoryStack = observer(({ session }: { session: any }): React.ReactElement => {
    const { t } = useTranslation('leaks');
    const [zoomData, setZoomData] = useState<Array<[number, number]>>([]);
    const [zoomMinTime, setZoomMinTime] = useState<number>(Number.MAX_SAFE_INTEGER);
    const [zoomMaxTime, setZoomMaxTime] = useState<number>(Number.MIN_SAFE_INTEGER);
    const [dataZoomKey, setDataZoomKey] = useState(0);
    const [selectedRange, setSelectedRange] = useState<[number, number] | undefined>(undefined);
    const selectedRangeRef = useRef<[number, number] | undefined>(undefined);
    const zoomRangeRef = useRef({ min: Number.MAX_SAFE_INTEGER, max: Number.MIN_SAFE_INTEGER });
    const debouncedFuncRangeRef = useRef<DebouncedFunc<(range: [number, number]) => void> | null>(null);
    const debouncedCommitRangeRef = useRef<DebouncedFunc<(range: [number, number]) => void> | null>(null);
    const debouncedLeakStatsRef = useRef<DebouncedFunc<(range: [number, number]) => void> | null>(null);
    const funcRangeRequestSeqRef = useRef(0);

    const schedulePotentialLeakStats = (range: [number, number]): void => {
        if (session.module !== 'memsnapshot') {
            return;
        }
        debouncedLeakStatsRef.current?.(range);
    };

    const requestPotentialLeakStatsNow = (range: [number, number]): void => {
        if (session.module !== 'memsnapshot') {
            return;
        }
        debouncedLeakStatsRef.current?.cancel();
        getPotentialLeakStats(session, range);
    };

    const commitSessionRange = (range: [number, number]): void => {
        runInAction(() => {
            session.minTime = range[0];
            session.maxTime = range[1];
        });
    };

    const commitRangeSideEffects = (range: [number, number]): void => {
        commitSessionRange(range);
        if (session.module === 'memsnapshot') {
            runInAction(() => {
                if (session.autoFilterPotentialLeaks) {
                    session.blocksCurrentPage = 1;
                    session.eventsCurrentPage = 1;
                }
            });
            if (session.autoFilterPotentialLeaks) {
                getBlockTableData(session);
                getEventTableData(session);
            }
        }
    };

    const getCurrentFuncRange = (): [number, number] => {
        return selectedRangeRef.current ?? [session.minTime, session.maxTime];
    };

    const requestFuncRangeData = (range: [number, number]): void => {
        if (!isValidRange(range)) {
            return;
        }
        const requestSeq = ++funcRangeRequestSeqRef.current;
        getFuncNewData(session, range[0], range[1], () => requestSeq === funcRangeRequestSeqRef.current);
    };

    const scheduleRangeChange = (range: [number, number]): void => {
        if (!isValidRange(range)) {
            return;
        }
        if (isSameRange(selectedRangeRef.current, range)) {
            return;
        }
        selectedRangeRef.current = range;
        setSelectedRange(range);
        schedulePotentialLeakStats(range);
        debouncedFuncRangeRef.current?.cancel();
        debouncedFuncRangeRef.current?.(range);
        debouncedCommitRangeRef.current?.cancel();
        debouncedCommitRangeRef.current?.(range);
    };

    if (debouncedFuncRangeRef.current === null) {
        debouncedFuncRangeRef.current = debounce((range: [number, number]): void => {
            requestFuncRangeData(range);
        }, 500);
    }
    if (debouncedCommitRangeRef.current === null) {
        debouncedCommitRangeRef.current = debounce((range: [number, number]): void => {
            commitRangeSideEffects(range);
        }, 300);
    }
    if (debouncedLeakStatsRef.current === null) {
        debouncedLeakStatsRef.current = debounce((range: [number, number]): void => {
            getPotentialLeakStats(session, range);
        }, 150, { maxWait: 500 });
    }

    const selectedZoomChange = (range: [number, number]): void => {
        if (!isValidRange(range)) {
            return;
        }
        scheduleRangeChange(range);

        const { sizeInfo, renderOptions } = session.leaksWorkerInfo;
        const newScale = range[1] - range[0] === 0 ? Number.MAX_SAFE_INTEGER : (sizeInfo.maxTimestamp - sizeInfo.minTimestamp) / (range[1] - range[0]);
        const newX = -(range[0] - sizeInfo.minTimestamp) * renderOptions.zoom.x * newScale;
        const transform = { x: newX, y: 0, scaleX: newScale, scaleY: 1 };

        runInAction(() => {
            session.leaksWorkerInfo.renderOptions.transform = transform;
        });
        workerTransform({ transform });
    };

    const syncDataZoomRange = (transform: RenderOptions['transform'], _source: TransformChangeSource = 'wheel'): void => {
        const { renderOptions } = session.leaksWorkerInfo;
        const { scaleX } = transform;
        const { viewport, zoom } = renderOptions;
        if (scaleX <= 0 || zoom.x <= 0 || viewport.width <= 0) {
            return;
        }
        const { min, max } = zoomRangeRef.current;
        if (min >= max) {
            return;
        }
        const visibleRange = viewport.width / zoom.x / scaleX;
        const visibleMinTime = zoom.offset - transform.x / scaleX / zoom.x;
        const range = clampRangeToBounds([visibleMinTime, visibleMinTime + visibleRange], min, max);
        if (!isValidRange(range)) {
            return;
        }
        scheduleRangeChange(range);
    };

    useEffect(() => {
        const newIdOpts = Object.keys(session.deviceIds).map((id: string) => ({ label: id, value: id }));
        if (newIdOpts.length > 0) {
            const newTypeOpts = session.deviceIds[newIdOpts[0].value].map((type: string) => ({ label: type, value: type }));
            const newThreadOpts = session.threadIds.map((thread: number) => ({ label: thread, value: thread }));
            runInAction(() => {
                session.deviceIdOpts = newIdOpts;
                session.typeOpts = newTypeOpts;
                session.threadOps = newThreadOpts;
                session.deviceId = newIdOpts[0].value;
                session.eventType = newTypeOpts[0].value;
                session.threadId = newThreadOpts[0]?.value ?? '';
            });
        }
        return () => {
        };
    }, [session.deviceIds, session.threadIds]);

    useEffect(() => {
        if (session.deviceId === '' || session.threadFlag) return;
        debouncedFuncRangeRef.current?.cancel();
        selectedRangeRef.current = undefined;
        setSelectedRange(undefined);
        setDataZoomKey(key => key + 1);
        getBarNewData(session);
    }, [session.deviceId, session.eventType, session.threadId]);

    useEffect(() => {
        setZoomData(session.allocationData.allocations.map((item: any) => ([item.timestamp, item.totalSize])));
        const { minTime, maxTime } = getInitialZoomDomain({
            blockMinTimestamp: session.leaksWorkerInfo.sizeInfo.minTimestamp,
            blockMaxTimestamp: session.leaksWorkerInfo.sizeInfo.maxTimestamp,
            allocationMinTimestamp: session.allocationData.minTimestamp,
            allocationMaxTimestamp: session.allocationData.maxTimestamp,
            funcMinTimestamp: session.funcData.minTimestamp,
            funcMaxTimestamp: session.funcData.maxTimestamp,
        });
        setZoomMinTime(minTime);
        setZoomMaxTime(maxTime);
        zoomRangeRef.current = { min: minTime, max: maxTime };
        if (selectedRange === undefined) {
            commitRangeSideEffects([minTime, maxTime]);
            getPotentialLeakStats(session, [minTime, maxTime]);
        }
    }, [
        session.allocationData.allocations,
        session.leaksWorkerInfo.sizeInfo.minTimestamp,
        session.leaksWorkerInfo.sizeInfo.maxTimestamp,
        session.funcData.minTimestamp,
        session.funcData.maxTimestamp,
        selectedRange,
    ]);

    useEffect(() => {
        return () => {
            debouncedFuncRangeRef.current?.cancel();
            debouncedCommitRangeRef.current?.cancel();
            debouncedLeakStatsRef.current?.cancel();
        };
    }, []);

    return (
        <>
            <CollapsiblePanel title={t('FlameGraph')} style={{ minWidth: 1000, display: session.threadOps.length > 0 && session.threadId !== '' ? 'block' : 'none' }}>
                <Label name={t('ThreadID')} />
                <Select
                    id={'select-threadId'}
                    value={session.threadId}
                    size="middle"
                    onChange={(value): void => {
                        runInAction(() => {
                            session.threadId = value;
                            session.threadFlag = false;
                            session.searchFunc = [];
                        });
                    }}
                    options={session.threadOps}
                />
                <Label name={t('Search')} style={{ marginLeft: 24 }} />
                <Select
                    id={'select-funcName'}
                    mode="multiple"
                    value={session.searchFunc}
                    style={{ width: 550, marginRight: 20 }}
                    onChange={(val: string[]): void => {
                        runInAction(() => { session.searchFunc = val; });
                    }}
                    options={session.funcOptions}
                    showSearch={true}
                    maxTagTextLength={10}
                    maxTagCount={4}
                />
                <Checkbox
                    checked={session.allowTrim}
                    onChange={(event: CheckboxChangeEvent): void => {
                        runInAction(() => { session.allowTrim = event.target.checked; });
                        requestFuncRangeData(getCurrentFuncRange());
                    }}
                >{t('allowTrim')}</Checkbox>
                <div id="funcContent" style={{ overflow: 'hidden', padding: 0, position: 'relative' }}>
                    <MemoryFunctionCall session={session} />
                </div>
            </CollapsiblePanel >
            <div data-testid="blockDiagramPanel">
                <CollapsiblePanel title={t('BlockGraph')} style={{ minWidth: 1000 }}>
                    <Label name={t('DeviceID')} />
                    <Select
                        id={'select-deviceId'}
                        style={{ marginRight: 20 }}
                        value={session.deviceId}
                        size="middle"
                        onChange={(value): void => {
                            runInAction(() => {
                                session.threadFlag = false;
                                session.typeOpts = session.deviceIds[value].map((type: string) => ({ label: type, value: type }));
                                session.deviceId = value;
                                session.eventType = session.deviceIds[value][0];
                            });
                        }}
                        options={session.deviceIdOpts}
                    />
                    <Label name={t('Type')} />
                    <Select
                        id={'select-type'}
                        value={session.eventType}
                        size="middle"
                        onChange={(value): void => {
                            runInAction(() => {
                                session.threadFlag = false;
                                session.eventType = value;
                            });
                        }}
                        options={session.typeOpts}
                    />
                    {session.module === 'memsnapshot' ? <PotentialLeakStats session={session} /> : <></>}
                    <div id="barContent" style={{ overflow: 'hidden', padding: 0, position: 'relative' }}>
                        <MemoryBlockDiagram
                            session={session}
                            onResetTransform={() => {
                                const { min, max } = zoomRangeRef.current;
                                const fullRange: [number, number] = [min, max];
                                if (!isValidRange(fullRange)) {
                                    return;
                                }
                                selectedRangeRef.current = fullRange;
                                setSelectedRange(fullRange);
                                debouncedFuncRangeRef.current?.cancel();
                                debouncedCommitRangeRef.current?.cancel();
                                debouncedLeakStatsRef.current?.cancel();
                                commitRangeSideEffects(fullRange);
                                requestFuncRangeData(fullRange);
                                requestPotentialLeakStatsNow(fullRange);
                                setDataZoomKey(key => key + 1);
                            }}
                            onTransformChange={syncDataZoomRange}
                        />
                        <MemoryDataZoom
                            key={dataZoomKey}
                            module={session.module}
                            offsetLeft={95}
                            offsetRight={105}
                            dataSource={zoomData}
                            minTime={zoomMinTime}
                            maxTime={zoomMaxTime}
                            selectedRange={selectedRange}
                            selectedZoomChange={selectedZoomChange} />
                    </div>
                </CollapsiblePanel>
            </div>
            {session.memoryStamp && session.module === 'leaks' && session.eventType !== 'HOST_PINNED'
                ? (
                    <CollapsiblePanel title={t('DetailsDiagram')} collapsible style={{ minWidth: 1000 }}>
                        <div id="detailsContent" style={{ position: 'relative' }}>
                            <div style={{ position: 'absolute', left: '42%' }}>{`${t('Current Time')}: ${convertNanoseconds(session.memoryStamp)}`}</div>
                            <MemorySliceChart session={session} />
                        </div>
                    </CollapsiblePanel>
                )
                : (
                    <></>
                )
            }
            {session.module === 'memsnapshot' &&
                <CollapsiblePanel title={t('stateDiagram')} testId="stateDiagramPanel" style={{ minWidth: 1000 }}>
                    <MemoryStateDiagram session={session} />
                </CollapsiblePanel>
            }
        </>
    );
});

export default MemoryStack;