import React, { useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from 'react';
import { Table, AugmentedNodeAttributes } from './components/Table';
import { VisualizationPropTypes, VISComponentType, VisualizationSettingsPropTypes } from '../../common';
import { Input } from '@graphpolaris/shared/lib/components/inputs';
import { SettingsContainer } from '@graphpolaris/shared/lib/vis/components/config';
import { Button } from '@graphpolaris/shared/lib/components/buttons';
import { useSearchResultData } from '@graphpolaris/shared/lib/data-access';
import { EntityPill } from '@graphpolaris/shared/lib/components/pills/Pill';
import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '@graphpolaris/shared/lib/components/accordion';
import html2canvas from 'html2canvas';

export interface TableVisHandle {
  exportImageInternal: () => void;
}

export type TableProps = {
  id: string;
  name: string;
  showBarplot: boolean;
  itemsPerPage: number;
  displayAttributes: string[];
  displayEntity: string;
  maxBarsCount: number;
};

const settings: TableProps = {
  id: 'TableVis',
  name: 'TableVis',
  itemsPerPage: 10,
  showBarplot: true,
  displayAttributes: [],
  displayEntity: '',
  maxBarsCount: 10,
};

export const TableVis = forwardRef<TableVisHandle, VisualizationPropTypes<TableProps>>(
  ({ data, schema, settings, updateSettings, graphMetadata }, refExternal) => {
    const searchResults = useSearchResultData();
    const ref = useRef<HTMLDivElement>(null);
    useEffect(() => {
      if (graphMetadata != undefined && settings.displayEntity === '') {
        if (!graphMetadata.nodes.labels.includes(settings.displayEntity)) {
          updateSettings({
            displayEntity: graphMetadata.nodes.labels[0],
            displayAttributes: Object.keys(graphMetadata.nodes.types[graphMetadata.nodes.labels[0]].attributes),
          });
        }
      }
    }, [graphMetadata, data, settings]);

    const attributesArray = useMemo<AugmentedNodeAttributes[]>(() => {
      //const similiarityThreshold = 0.9;
      let displayAttributesSorted: string[];

      displayAttributesSorted = [...settings.displayAttributes].sort((a, b) => a.localeCompare(b));

      const dataNodes = (searchResults?.nodes?.length ?? 0) === 0 ? data.nodes : searchResults.nodes;

      return (
        dataNodes
          .filter((node) => {
            // some dataset do not have label field
            let labelNode = '';
            if (node.label !== undefined) {
              labelNode = node.label;
            } else {
              const idParts = node._id.split('/');
              labelNode = idParts[0];
            }
            return labelNode === settings.displayEntity;
          })
          ///.filter((obj) => obj.similarity === undefined || obj.similarity >= similiarityThreshold)
          .map((node) => {
            // get attributes filtered and sorted
            const filteredAttributes = Object.fromEntries(
              Object.entries(node.attributes)
                .filter(([attr]) => settings.displayAttributes.includes(attr))
                .sort(([attrA], [attrB]) => settings.displayAttributes.indexOf(attrA) - settings.displayAttributes.indexOf(attrB)),
            );

            // doubled types structure to handle discrepancies in schema object in sb and dev env.

            let types =
              schema.nodes.find((n: any) => {
                let labelNode = node.label;
                return labelNode === n.key;
              })?.attributes?.attributes ??
              schema.nodes.find((n: any) => {
                let labelNode = node.label;

                return labelNode === n.name;
              })?.attributes;

            if (types) {
              return {
                attribute: filteredAttributes,
                type: Object.fromEntries(types.map((t: any) => [t.name, t.type])),
              };
            } else {
              return {
                attribute: filteredAttributes,
                type: {},
              };
            }
          })
      );
    }, [data.nodes, settings.displayEntity, settings.displayAttributes, searchResults]);

    const exportImageInternal = () => {
      if (ref.current) {
        // Check if divRef.current is not null
        html2canvas(ref.current).then((canvas) => {
          const pngData = canvas.toDataURL('image/png');
          const a = document.createElement('a');
          a.href = pngData;
          a.download = 'tablevis.png';
          a.click();
        });
      } else {
        console.error('The referenced div is null.');
      }
    };

    useImperativeHandle(refExternal, () => ({
      exportImageInternal,
    }));

    return (
      <div className="h-full w-full" ref={ref}>
        {attributesArray.length > 0 && (
          <Table
            data={attributesArray}
            itemsPerPage={settings.itemsPerPage}
            showBarPlot={settings.showBarplot}
            showAttributes={settings.displayAttributes}
            selectedEntity={settings.displayEntity}
            maxBarsCount={settings.maxBarsCount}
          />
        )}
      </div>
    );
  },
);

const TableSettings = ({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<TableProps>) => {
  useEffect(() => {
    if (graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0 && settings.displayEntity === '') {
      updateSettings({ displayEntity: graphMetadata.nodes.labels[0] });
    }
  }, [graphMetadata]);

  const selectedNodeAttributes = useMemo(() => {
    if (settings.displayEntity) {
      const nodeType = graphMetadata.nodes.types[settings.displayEntity];
      if (nodeType && nodeType.attributes) {
        return Object.keys(nodeType.attributes).sort((a, b) => a.localeCompare(b));
      }
    }
    return [];
  }, [settings.displayEntity, graphMetadata]);

  useEffect(() => {
    if (graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0 && settings.displayAttributes.length === 0) {
      updateSettings({ displayAttributes: selectedNodeAttributes });
    }
  }, [selectedNodeAttributes, graphMetadata]);

  return (
    <SettingsContainer>
      <div className="my-2">
        <Input
          className="w-full text-justify justify-center"
          type="dropdown"
          value={settings.displayEntity}
          options={graphMetadata.nodes.labels}
          onChange={(val) => updateSettings({ displayEntity: val as string })}
          overrideRender={
            <EntityPill
              title={
                <div className="flex flex-row justify-between items-center cursor-pointer">
                  <span>{settings.displayEntity || ''}</span>
                  <Button variantType="secondary" variant="ghost" size="2xs" iconComponent="icon-[ic--baseline-arrow-drop-down]" />
                </div>
              }
            />
          }
        ></Input>
        <div className="my-2">
          <Input
            type="boolean"
            label="Show barplot"
            value={settings.showBarplot}
            onChange={(val) => updateSettings({ showBarplot: val })}
          />
        </div>
        <div className="my-2">
          <Input
            type="dropdown"
            label="Items per page"
            value={settings.itemsPerPage}
            onChange={(val) => updateSettings({ itemsPerPage: val as number })}
            options={[10, 25, 50, 100]}
          />
        </div>
        <div className="my-2">
          <Input
            type="number"
            label="Max Bars in Bar Plots"
            value={settings.maxBarsCount}
            onChange={(val) => updateSettings({ maxBarsCount: val })}
          />
        </div>
        <Accordion>
          <AccordionItem>
            <AccordionHead>
              <span className="text-sm">Attributes to display:</span>
            </AccordionHead>
            <AccordionBody>
              <Input
                type="checkbox"
                value={settings.displayAttributes}
                options={selectedNodeAttributes}
                onChange={(val: string[] | string) => {
                  const updatedVal = Array.isArray(val) ? val : [val];
                  updateSettings({ displayAttributes: updatedVal });
                }}
              />
            </AccordionBody>
          </AccordionItem>
        </Accordion>
      </div>
    </SettingsContainer>
  );
};
const tableRef = React.createRef<{ exportImageInternal: () => void }>();

export const TableComponent: VISComponentType<TableProps> = {
  displayName: 'TableVis',
  description: 'Node Attribute Statistics and Details',
  component: React.forwardRef((props: VisualizationPropTypes<TableProps>, ref) => <TableVis {...props} ref={tableRef} />),
  settingsComponent: TableSettings,
  settings: settings,
  exportImage: () => {
    if (tableRef.current) {
      tableRef.current.exportImageInternal();
    } else {
      console.error('Map reference is not set.');
    }
  },
};

export default TableComponent;