import '../../styles/GridTheme.scss';
import '../../styles/index.scss';
import '@linzjs/lui/dist/scss/base.scss';
import '@linzjs/lui/dist/fonts';

import type { Meta, StoryFn } from '@storybook/react-vite';
import { useCallback, useContext, useMemo, useState } from 'react';
import { expect, screen, userEvent, within } from 'storybook/test';

import {
  ActionButton,
  ColDefT,
  Grid,
  GridCell,
  GridContext,
  GridContextProvider,
  GridInlineTextInput,
  GridPopoverMenu,
  GridProps,
  GridUpdatingContextProvider,
  wait,
} from '../..';
import { waitForGridReady } from '../../utils/__tests__/storybookTestUtil';

interface IFormTestRow {
  id: number;
  text1: string | null;
  text2: string | null;
}

const initialRows: IFormTestRow[] = [
  { id: 1000, text1: 'a text1', text2: 'a text2' },
  { id: 1001, text1: 'b text1', text2: 'b text2' },
];

export default {
  title: 'Components / Grids',
  component: Grid,
  args: {
    quickFilterValue: '',
    selectable: true,
  },
  decorators: [
    (Story) => (
      <div style={{ width: 1024, height: 400 }}>
        <GridUpdatingContextProvider>
          <GridContextProvider>
            <Story />
          </GridContextProvider>
        </GridUpdatingContextProvider>
      </div>
    ),
  ],
} as Meta<typeof Grid>;

const GridInlineTextTemplate: StoryFn<typeof Grid<IFormTestRow>> = (props: GridProps<IFormTestRow>) => {
  const { selectRowsWithFlashDiff } = useContext(GridContext);
  const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
  const [rowData, setRowData] = useState<IFormTestRow[]>(initialRows);

  const columnDefs: ColDefT<IFormTestRow>[] = useMemo(
    () => [
      GridCell({
        field: 'id',
        headerName: 'Id',
      }),
      GridInlineTextInput(
        {
          field: 'text1',
          headerName: 'Text input 1',
        },
        {
          editorParams: {
            placeholder: 'Enter some text...',
            onSave: ({ selectedRowIds, value }) => {
              setRowData(rowData.map((data) => (selectedRowIds.includes(data.id) ? { ...data, text1: value } : data)));
              return true;
            },
          },
        },
      ),
      GridInlineTextInput(
        {
          field: 'text2',
          headerName: 'Text input 2',
        },
        {
          editorParams: {
            required: true,
            maxLength: 12,
            placeholder: 'Enter some text...',
            invalid: (value: string) => {
              if (value === 'never') return "The value 'never' is not allowed";
              return null;
            },
            onSave: ({ selectedRowIds, value }) => {
              setRowData(rowData.map((data) => (selectedRowIds.includes(data.id) ? { ...data, text2: value } : data)));
              return true;
            },
          },
        },
      ),
      GridPopoverMenu(
        {},
        {
          multiEdit: true,
          editorParams: {
            defaultAction: ({ menuOption }) => {
              // eslint-disable-next-line no-console
              console.log('clicked', { menuOption });
            },
            options: async (selectedItems) => {
              // Just doing a timeout here to demonstrate deferred loading
              await wait(500);
              return [
                {
                  label: 'Test',
                  action: () => {
                    // empty
                  },
                  disabled: selectedItems.length > 1,
                },
              ];
            },
          },
        },
      ),
    ],
    [rowData],
  );

  const addRowAction = useCallback(async () => {
    await wait(1000);

    const lastRow = rowData[rowData.length - 1];
    // eslint-disable-next-line @typescript-eslint/require-await
    await selectRowsWithFlashDiff(async () => {
      setRowData([
        ...rowData,
        {
          id: (lastRow?.id ?? 0) + 1,
          text1: '?',
          text2: '?',
        },
      ]);
    });
  }, [rowData, selectRowsWithFlashDiff]);

  return (
    <>
      <Grid
        {...props}
        enableRangeSelection={true}
        enableMultilineBulkEdit={true}
        hideSelectedRow={true}
        externalSelectedItems={externalSelectedItems}
        setExternalSelectedItems={setExternalSelectedItems}
        columnDefs={columnDefs}
        rowData={rowData}
        domLayout={'autoHeight'}
        defaultColDef={{ minWidth: 70 }}
        sizeColumns={'auto'}
        singleClickEdit={true}
        onBulkEditingComplete={() => {
          /* eslint-disable-next-line no-console */
          console.log('onBulkEditingComplete()');
        }}
      />
      <ActionButton icon={'ic_add'} name={'Add new row'} inProgressName={'Adding...'} onClick={addRowAction} />
    </>
  );
};

export const _EditInlineText = GridInlineTextTemplate.bind({});
_EditInlineText.play = waitForGridReady;

const InlineText_ChangeEventsTemplate: StoryFn<typeof Grid<IFormTestRow>> = (props: GridProps<IFormTestRow>) => {
  const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
  const [isDirty, setIsDirty] = useState(false);
  const [rowData, setRowData] = useState<IFormTestRow[]>(initialRows);

  // Use a named handler for onChange
  const handleInlineEditChange = () => {
    setIsDirty(true);
  };

  const columnDefs: ColDefT<IFormTestRow>[] = useMemo(
    () => [
      GridCell({
        field: 'id',
        headerName: 'Id',
      }),
      GridInlineTextInput(
        {
          field: 'text1',
          headerName: 'Text input 1',
        },
        {
          editorParams: {
            placeholder: 'Enter some text...',
            onChange: handleInlineEditChange,
            onSave: ({ selectedRowIds, value }) => {
              setRowData(rowData.map((data) => (selectedRowIds.includes(data.id) ? { ...data, text1: value } : data)));
              return true;
            },
          },
        },
      ),
      GridInlineTextInput(
        {
          field: 'text2',
          headerName: 'Text input 2',
        },
        {
          editorParams: {
            placeholder: 'Enter some text...',
            onChange: handleInlineEditChange,
            onSave: ({ selectedRowIds, value }) => {
              setRowData(rowData.map((data) => (selectedRowIds.includes(data.id) ? { ...data, text2: value } : data)));
              return true;
            },
          },
        },
      ),
    ],
    [rowData],
  );

  return (
    <>
      <Grid
        {...props}
        selectable={true}
        hideSelectColumn={true}
        externalSelectedItems={externalSelectedItems}
        setExternalSelectedItems={setExternalSelectedItems}
        columnDefs={columnDefs}
        rowData={rowData}
        domLayout={'autoHeight'}
        defaultColDef={{ minWidth: 70 }}
        sizeColumns={'auto'}
        singleClickEdit={true}
        enableMultilineBulkEdit={true}
      />
      <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
        <button type="button" data-testid="inline-edit-save" disabled={!isDirty} onClick={() => setIsDirty(false)}>
          Save
        </button>
      </div>
    </>
  );
};

export const _InlineText_ChangeEvents = InlineText_ChangeEventsTemplate.bind({});
_InlineText_ChangeEvents.play = async (context) => {
  await waitForGridReady(context);
  const canvas = within(context.canvasElement);
  const saveButtons = canvas.getAllByTestId('inline-edit-save');
  const saveButton = saveButtons[0];
  await expect(saveButton).toBeDisabled();
  const textCells = canvas.getAllByText('a text1');
  await userEvent.click(textCells[0]);
  const input = await screen.findByPlaceholderText('Enter some text...');
  await userEvent.type(input, 'X');
  await expect(saveButton).toBeEnabled();
};
