import { useCallback, useMemo, useState } from 'react';

import { useLocation, useNavigate, useSearchParams } from 'react-router';
import { CellContext } from '@tanstack/react-table';
import useEntryAdminActions, { type UseEntryAdminActionsOptions } from '@features/entry/hooks/use-entry-admin-actions';
import useEntryAdminBulkActions, {
  type UseEntryAdminBulkActionsOptions,
} from '@features/entry/hooks/use-entry-admin-bulk-actions';
import useEntryAdminMenus, { type UseEntryAdminMenusOptions } from '@features/entry/hooks/use-entry-admin-menus';
import { nl2br } from '@ablogcms/react-utils';
import { notify } from '@ablogcms/notify';
import useUpdateEffect from '@ablogcms/react-hooks/use-update-effect';
import { DataView } from '@ablogcms/components/dataview';
import { components } from '@ablogcms/components/dataview/components';
import {
  type DataviewComponents,
  type ColumnServiceInterface,
  type View,
  type ColumnConfig,
  type Column,
  type ColumnType,
  type DragEndContext,
  type GetValues,
  type CreateGetValuesOptions,
  CreateGetValues,
} from '@ablogcms/components/dataview/types';
import Alert from '@ablogcms/components/alert';
import type { EntriesResponse, EntryType } from '../../types';
import { ENTRY_COLUMNS } from '../../constants';
import EntryFilter from '../entry-filter';
import EntryCreateForm from '../entry-create-form';
import useEntryCustomColumnsSWR from '../../hooks/use-entry-custom-columns-swr';
import useEntryColumnConfigSWR from '../../hooks/use-entry-column-config-swr';
import { useAcmsContext } from '../../../../stores/acms';
import useEntriesSWR from '../../hooks/use-entries-swr';
import { getEntryColumnTypes, getOrderInfo, getSortFromContext } from '../../utils';
import { reorderCategoryEntry, reorderEntry, reorderUserEntry } from '../../api';

interface EntryAdminProps {
  /**
   * カスタムカラムの値を取得する関数
   */
  getValues?: (getValues: GetValues<EntryType>) => GetValues<EntryType>;
  /**
   * カスタムカラムの値を取得する関数のオプション
   */
  getValuesOptions?: CreateGetValuesOptions;
  /**
   * 各行のアクションをカスタマイズする関数
   */
  getActions?: UseEntryAdminActionsOptions['getActions'];
  /**
   * 一括操作のアクションをカスタマイズする関数
   */
  getBulkActions?: UseEntryAdminBulkActionsOptions['getBulkActions'];
  /**
   * メニューをカスタマイズする関数
   */
  getMenus?: UseEntryAdminMenusOptions['getMenus'];
  /**
   * 表示するカラムをカスタマイズする関数
   */
  getColumns?: (columns: Column<EntryType>[]) => Column<EntryType>[];
  /**
   * 行選択を有効にするかどうか
   */
  enableRowSelection?: React.ComponentProps<typeof DataView<EntryType>>['enableRowSelection'];
  /**
   * 行ドラッグを有効にするかどうか
   */
  enableRowDnD?: React.ComponentProps<typeof DataView<EntryType>>['enableRowDnD'];
  /**
   * 行IDを取得する関数
   */
  getRowId?: React.ComponentProps<typeof DataView<EntryType>>['getRowId'];
  /**
   * ページネーションを有効にするかどうか
   */
  enablePagination?: React.ComponentProps<typeof DataView<EntryType>>['enablePagination'];
  /**
   * カスタムコンポーネントをカスタマイズする関数
   */
  components?: Partial<DataviewComponents<EntryType>>;
}

// eslint-disable-next-line react/function-component-definition -- 変数の型注釈（DataviewComponents<T>['EmptyState']）を維持するため関数宣言への変換を見送る
const EmptyState: DataviewComponents<EntryType>['EmptyState'] = () => {
  const { context } = useAcmsContext();

  return (
    <components.EmptyState
      title={ACMS.i18n('entry_index.empty_state_title')}
      message={nl2br(ACMS.i18n('entry_index.empty_state_message'))}
    >
      <EntryCreateForm blogId={context.bid || parseInt(ACMS.Config.bid, 10)} categoryId={context.cid} />
    </components.EmptyState>
  );
};

function defaultGetRowId(row: EntryType) {
  return `${row.blog.id}:${row.id}`;
}

function createGetGetValue(options: Pick<EntryAdminProps, 'getValuesOptions' | 'getValues'>) {
  return function getGetValue(createGetValues: CreateGetValues<EntryType>) {
    return (info: CellContext<EntryType, unknown>): React.ReactNode => {
      let getValues = createGetValues(options.getValuesOptions);
      if (options.getValues) {
        getValues = typeof options.getValues === 'function' ? options.getValues(getValues) : options.getValues;
      }
      const getValue = getValues[info.column.columnDef.meta?.type as ColumnType];
      return typeof getValue === 'function' ? getValue(info) : (info.getValue() as React.ReactNode);
    };
  };
}

function EntryAdmin(props: EntryAdminProps) {
  const {
    getValues,
    getValuesOptions,
    getActions = (actions) => actions,
    getBulkActions = (bulkActions) => bulkActions,
    getMenus = (menus) => menus,
    getColumns = (columns) => columns,
    enableRowSelection,
    enableRowDnD,
    getRowId = defaultGetRowId,
    enablePagination = true,
    components = {},
  } = props;
  const { context } = useAcmsContext();
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  const location = useLocation();
  const { data, isLoading: isEntriesLoading, invalidate, mutate } = useEntriesSWR(context, searchParams);
  const {
    customColumns,
    mutate: mutateCustomColumns,
    isLoading: isCustomColumnLoading,
    invalidate: invalidateCustomColumns,
  } = useEntryCustomColumnsSWR();
  const {
    config,
    mutate: mutateColumnConfig,
    isLoading: isColumnConfigLoading,
    invalidate: invalidateColumnConfig,
  } = useEntryColumnConfigSWR();

  const isLoading = useMemo(
    () => isCustomColumnLoading || isColumnConfigLoading || isEntriesLoading,
    [isColumnConfigLoading, isCustomColumnLoading, isEntriesLoading]
  );
  const columns = useMemo<Column<EntryType>[]>(() => {
    const columns = [...ENTRY_COLUMNS, ...(customColumns || [])];
    return getColumns(columns);
  }, [customColumns, getColumns]);
  const columnTypes = useMemo(() => getEntryColumnTypes(), []);
  const columnVisibility = useMemo<ColumnConfig['visibility']>(() => {
    const defaultCustomColumnVisibility = (customColumns || []).reduce((acc, column) => {
      // カスタムカラムのデフォルトの表示状態は非表示
      return { ...acc, [column.id]: false };
    }, {});
    return {
      ...defaultCustomColumnVisibility,
      ...config.visibility,
    };
  }, [customColumns, config.visibility]);

  const columnService: ColumnServiceInterface<EntryType> = useMemo(
    () => ({
      id: 'entry',
      columns,
      config: { visibility: columnVisibility, order: config.order },
      mutateCustomColumns,
      invalidateCustomColumns,
      mutateConfig: mutateColumnConfig,
      invalidateColumnConfig,
    }),
    [
      columns,
      columnVisibility,
      mutateCustomColumns,
      invalidateCustomColumns,
      config.order,
      mutateColumnConfig,
      invalidateColumnConfig,
    ]
  );

  const actions = useEntryAdminActions({
    getActions,
    context: { invalidate },
  });

  const bulkActions = useEntryAdminBulkActions({
    getBulkActions,
    context: { invalidate, bulkActions: data?.bulkActions || [], sort: data?.sort },
  });

  const menus = useEntryAdminMenus({
    getMenus,
    context: { context, searchParams, sort: data?.sort, columnService },
  });

  const view = useMemo<View>(() => {
    return {
      type: 'table',
      search: '',
      sort: getSortFromContext(context, searchParams, columns),
      pageIndex: context.page || 0,
      // defaultLimit は管理画面のみで出力されるが、このコンポーネントは管理画面専用のため必ず存在する
      pageSize: context.limit || parseInt(ACMS.Config.defaultLimit!, 10),
      visibility: columnVisibility,
      order: ['sort', ...config.order],
    };
  }, [columnVisibility, searchParams, context, config.order, columns]);

  const handleViewChange = (newView: View) => {
    const orderInfo = getOrderInfo(newView.sort, columns);
    if (orderInfo.sortFieldName) {
      searchParams.set('sortFieldName', orderInfo.sortFieldName);
    }
    navigate(
      ACMS.Library.acmsLink({
        ...context,
        page: newView.pageIndex,
        limit: newView.pageSize,
        order: orderInfo.order,
        searchParams,
      })
    );
  };

  const handleSubmit = useCallback(
    (event: React.FormEvent<HTMLFormElement>) => {
      event.preventDefault();
      const form = event.currentTarget;
      const formData = new FormData(form, (event.nativeEvent as SubmitEvent).submitter);
      const intent = formData.get('intent') as 'search' | 'clear';

      if (intent === 'clear') {
        form.reset();
        navigate(ACMS.Library.acmsLink({ bid: ACMS.Config.bid, admin: ACMS.Config.admin || 'entry_index' }));
        return;
      }

      const { context, searchParams } = ACMS.Library.createAcmsContextFromFormData(formData);
      navigate(ACMS.Library.acmsLink({ ...context, searchParams }));
    },
    [navigate]
  );

  const handleDragEnd = useCallback(
    async (entries: EntryType[], prevEntries: EntryType[], context: DragEndContext<EntryType>) => {
      if (data?.sort.type === undefined) {
        return;
      }

      // 表示順の採番はサーバー側で行う。一覧はページングされているため、表示中のページの中だけで
      // 番号を振り直すと次のページの表示順と衝突する。クライアントは移動先の前後関係だけを伝える。
      // 降順で表示している場合は画面上の前後と表示順の大小が逆になるため入れ替える。
      const isDescending = view.sort?.direction === 'desc';
      const prevEntry = isDescending ? context.next : context.prev;
      const nextEntry = isDescending ? context.prev : context.next;

      const formData = new FormData();
      formData.append('eid', context.moved.id.toString());
      if (prevEntry !== null) {
        formData.append('prev_eid', prevEntry.id.toString());
      }
      if (nextEntry !== null) {
        formData.append('next_eid', nextEntry.id.toString());
      }

      async function reorderFn(formData: FormData, data: EntriesResponse): Promise<EntriesResponse> {
        await {
          entry: reorderEntry,
          user: reorderUserEntry,
          category: reorderCategoryEntry,
        }[data.sort.type](formData);
        return { ...data, entries };
      }
      try {
        await mutate(reorderFn(formData, data), {
          optimisticData: { ...data, entries },
          populateCache: true,
        });
        notify.info(ACMS.i18n('entry_index.bulk_action.feedback.order.success'));
      } catch (error) {
        notify.danger(ACMS.i18n('entry_index.bulk_action.feedback.order.error'));
        console.error(error);
      }
    },
    [view.sort?.direction, data, mutate]
  );

  const [isAlertVisible, setIsAlertVisible] = useState(true);

  // data.sort の変更を監視して isAlertVisible をリセット
  useUpdateEffect(() => {
    if (data?.sort.enabled && data.sort.type) {
      setIsAlertVisible(true);
    }
    // location.key を監視しているのは、同一のurlに遷移したときに isAlertVisible をリセットするため
  }, [data?.sort.enabled, data?.sort.type, location.key]);
  return (
    <div>
      <EntryFilter
        columns={columns}
        ignoredFilters={data?.ignoredFilters}
        sortType={data?.sort.enabled ? data.sort.type : undefined}
        onSubmit={handleSubmit}
      />
      {data?.sort.enabled && data.sort.type && isAlertVisible && (
        <Alert variant="info" onClose={() => setIsAlertVisible(false)}>
          {ACMS.i18n(`entry_index.sort_mode.alert.${data.sort.type}`, {
            name: data.sort.context?.name,
          })}
        </Alert>
      )}
      <DataView<EntryType>
        view={view}
        onViewChange={handleViewChange}
        data={data?.entries || []}
        columns={columns}
        actions={actions}
        bulkActions={bulkActions}
        menus={menus}
        enableRowSelection={typeof enableRowSelection === 'boolean' ? enableRowSelection : bulkActions.length > 0}
        enableRowDnD={typeof enableRowDnD === 'boolean' ? enableRowDnD : data?.sort.enabled}
        getRowId={getRowId}
        getGetValue={createGetGetValue({ getValuesOptions, getValues })}
        columnTypes={columnTypes}
        onDragEnd={handleDragEnd}
        enablePagination={enablePagination}
        paginationInfo={{
          totalItems: data?.totalItems || 0,
          // limitOptions は管理画面のみで出力されるが、このコンポーネントは管理画面専用のため必ず存在する
          pageSizes: ACMS.Config.limitOptions!.map((limit) => parseInt(limit, 10)),
        }}
        components={{
          EmptyState,
          ...components,
        }}
        isLoading={isLoading}
        aria-label={ACMS.i18n('entry_index.dataview.label')}
      />
    </div>
  );
}

export default EntryAdmin;
