// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { useHeight } from "@/app/hook/useHeight";
import { useTableNav } from "@table-nav/react";
import {
    createColumnHelper,
    flexRender,
    getCoreRowModel,
    getSortedRowModel,
    useReactTable,
} from "@tanstack/react-table";
import { clsx } from "clsx";
import Papa from "papaparse";
import { useEffect, useMemo, useRef, useState } from "react";

import "./csvview.less";

const MAX_DATA_SIZE = 10 * 1024 * 1024; // 10MB in bytes

type CSVRow = {
    [key: string]: string | number;
};

interface CSVViewProps {
    parentRef: React.MutableRefObject<HTMLDivElement>;
    content: string;
    filename: string;
    readonly: boolean;
}

interface State {
    content: string | null;
    showReadonly: boolean;
    tbodyHeight: number;
}

const columnHelper = createColumnHelper<any>();

// TODO remove parentRef dependency -- use own height
const CSVView = ({ parentRef, filename, content }: CSVViewProps) => {
    const csvCacheRef = useRef(new Map<string, string>());
    const rowRef = useRef<(HTMLTableRowElement | null)[]>([]);
    const headerRef = useRef<HTMLTableRowElement | null>(null);
    const probeRef = useRef<HTMLTableRowElement | null>(null);
    const tbodyRef = useRef<HTMLTableSectionElement | null>(null);

    const [state, setState] = useState<State>({
        content,
        showReadonly: true,
        tbodyHeight: 0,
    });

    const [tableLoaded, setTableLoaded] = useState(false);

    const { listeners } = useTableNav();
    const parentHeight = useHeight(parentRef);

    const cacheKey = `${filename}`;
    csvCacheRef.current.set(cacheKey, content);

    // Parse the CSV data
    const parsedData = useMemo<CSVRow[]>(() => {
        if (!state.content) return [];

        // Trim the content and then check for headers based on the first row's content.
        const trimmedContent = state.content.trim();
        const firstRow = trimmedContent.split("\n")[0];

        // This checks if the first row starts with a letter or a quote
        const hasHeaders = !!firstRow.match(/^[a-zA-Z"]/);

        const results = Papa.parse(trimmedContent, { header: hasHeaders });

        // Check for non-header CSVs
        if (!hasHeaders && Array.isArray(results.data) && Array.isArray(results.data[0])) {
            const dataArray = results.data as string[][]; // Asserting the type
            const headers = Array.from({ length: dataArray[0].length }, (_, i) => `Column ${i + 1}`);
            results.data = dataArray.map((row) => {
                const newRow: CSVRow = {};
                row.forEach((value, index) => {
                    newRow[headers[index]] = value;
                });
                return newRow;
            });
        }

        return results.data.map((row) => {
            return Object.fromEntries(
                Object.entries(row as CSVRow).map(([key, value]) => {
                    if (typeof value === "string") {
                        const numberValue = parseFloat(value);
                        if (!isNaN(numberValue) && String(numberValue) === value) {
                            return [key, numberValue];
                        }
                    }
                    return [key, value];
                })
            ) as CSVRow;
        });
    }, [state.content]);

    // Column Definitions
    const columns = useMemo(() => {
        if (parsedData.length === 0) {
            return [];
        }
        const headers = Object.keys(parsedData[0]);
        return headers.map((header) =>
            columnHelper.accessor(header, {
                header: () => header,
                cell: (info) => info.renderValue(),
            })
        );
    }, [parsedData]);

    useEffect(() => {
        if (probeRef.current && headerRef.current && parsedData.length && parentRef.current) {
            const rowHeight = probeRef.current.offsetHeight;
            const fullTBodyHeight = rowHeight * parsedData.length;
            const headerHeight = headerRef.current.offsetHeight;
            const maxHeightLessHeader = parentHeight - headerHeight;
            const tbodyHeight = Math.min(maxHeightLessHeader, fullTBodyHeight) - 3; // 3 for the borders

            setState((prevState) => ({ ...prevState, tbodyHeight }));
        }
    }, [parentHeight, parsedData]);

    // Makes sure rows are rendered before setting the renderer as loaded
    useEffect(() => {
        let tid: NodeJS.Timeout;

        if (rowRef.current.length === parsedData.length) {
            tid = setTimeout(() => {
                setTableLoaded(true);
            }, 50); // Delay a bit to make sure the rows are rendered
        }

        return () => clearTimeout(tid);
    }, [rowRef, parsedData]);

    const table = useReactTable({
        manualPagination: true,
        data: parsedData,
        columns,
        getCoreRowModel: getCoreRowModel(),
        getSortedRowModel: getSortedRowModel(),
    });

    return (
        <div className={clsx("csv-view", { show: tableLoaded })} style={{ height: "auto" }}>
            <table className="probe">
                <tbody>
                    <tr ref={probeRef}>
                        <td>dummy data</td>
                    </tr>
                </tbody>
            </table>
            <table {...listeners}>
                <thead>
                    {table.getHeaderGroups().map((headerGroup, index) => (
                        <tr key={headerGroup.id} ref={headerRef} id={headerGroup.id} tabIndex={index}>
                            {headerGroup.headers.map((header, index) => (
                                <th
                                    key={header.id}
                                    colSpan={header.colSpan}
                                    id={header.id}
                                    tabIndex={index}
                                    style={{ width: header.getSize() }}
                                >
                                    {header.isPlaceholder ? null : (
                                        <div
                                            {...{
                                                className: header.column.getCanSort()
                                                    ? "inner cursor-pointer select-none"
                                                    : "",
                                                onClick: header.column.getToggleSortingHandler(),
                                            }}
                                        >
                                            {flexRender(header.column.columnDef.header, header.getContext())}
                                            {header.column.getIsSorted() === "asc" ? (
                                                <i className="sort-icon fa-sharp fa-solid fa-sort-up"></i>
                                            ) : header.column.getIsSorted() === "desc" ? (
                                                <i className="sort-icon fa-sharp fa-solid fa-sort-down"></i>
                                            ) : null}
                                        </div>
                                    )}
                                </th>
                            ))}
                        </tr>
                    ))}
                </thead>
                <tbody style={{ height: `${state.tbodyHeight}px` }} ref={tbodyRef}>
                    {table.getRowModel().rows.map((row, index) => (
                        <tr key={row.id} ref={(el) => (rowRef.current[index] = el)} id={row.id} tabIndex={index}>
                            {row.getVisibleCells().map((cell) => (
                                <td key={cell.id} id={cell.id} tabIndex={index}>
                                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                                </td>
                            ))}
                        </tr>
                    ))}
                </tbody>
            </table>
        </div>
    );
};

export { CSVView };
