/**
 * This file is part of the NocoBase (R) project.
 * Copyright (c) 2020-2024 NocoBase Co., Ltd.
 * Authors: NocoBase Team.
 *
 * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
 * For more information, please refer to: https://www.nocobase.com/agreement.
 */

import { ArrayField } from '@formily/core';
import { ISchema, Schema, useForm } from '@formily/react';
import {
  CollectionFieldOptions,
  CollectionFieldOptions_deprecated,
  CollectionManager,
  DEFAULT_DATA_SOURCE_KEY,
  useACLRoleContext,
  useCollectionManager_deprecated,
  useDataSourceManager,
} from '@nocobase/client';
import { useContext, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ChartConfigContext } from '../configure';
import formatters from '../configure/formatters';
import { useChartsTranslation } from '../locale';
import { ChartRendererContext } from '../renderer';
import { getField, getSelectedFields, parseField, processData } from '../utils';

export type FieldOption = {
  value: string;
  label: string;
  key: string;
  alias?: string;
  name?: string;
  type?: string;
  interface?: string;
  uiSchema?: ISchema;
  target?: string;
  targetFields?: FieldOption[];
};

export const useChartDataSource = (dataSource?: string) => {
  const { current } = useContext(ChartConfigContext);
  const { dataSource: currentDataSource, collection } = current || {};
  dataSource = dataSource || currentDataSource || DEFAULT_DATA_SOURCE_KEY;
  const dm = useDataSourceManager();
  const ds = dm.getDataSource(dataSource);
  const fim = dm.collectionFieldInterfaceManager;
  const cm = ds?.collectionManager;
  return { cm, fim, collection };
};

export const useFields = (
  collectionFields: CollectionFieldOptions[],
): (CollectionFieldOptions & {
  key: string;
  label: string;
  value: string;
})[] => {
  const fields = (collectionFields || [])
    .filter((field) => {
      return field.interface;
    })
    .map((field) => ({
      ...field,
      key: field.name,
      label: field.uiSchema?.title || field.name,
      value: field.name,
    }));
  return fields;
};

export const useFieldsWithAssociation = (dataSource?: string, collection?: string) => {
  const { t } = useTranslation();
  const { cm, fim, collection: _collection } = useChartDataSource(dataSource);
  const collectionFields = cm.getCollectionFields(collection || _collection);
  const fields = useFields(collectionFields);
  return useMemo(
    () =>
      fields.map((field) => {
        const filterable = fim.getFieldInterface(field.interface)?.filterable;
        const label = Schema.compile(field.uiSchema?.title || field.name, { t });
        if (!(filterable && (filterable?.nested || filterable?.children?.length))) {
          return { ...field, label };
        }
        let targetFields = [];
        if (filterable?.nested) {
          const nestedFields = (cm.getCollectionFields(field.target) || [])
            .filter((targetField) => {
              return targetField.interface;
            })
            .map((targetField) => ({
              ...targetField,
              key: `${field.name}.${targetField.name}`,
              label: `${label} / ${Schema.compile(targetField.uiSchema?.title || targetField.name, { t })}`,
              value: `${field.name}.${targetField.name}`,
            }));
          targetFields = [...targetFields, ...nestedFields];
        }

        if (filterable?.children?.length) {
          const children = filterable.children.map((child: any) => ({
            ...child,
            key: `${field.name}.${child.name}`,
            label: `${label} / ${Schema.compile(child.schema?.title || child.title || child.name, { t })}`,
            value: `${field.name}.${child.name}`,
          }));
          targetFields = [...targetFields, ...children];
        }

        return {
          ...field,
          label,
          targetFields,
        };
      }),
    [fields],
  );
};

export const useChartFields = (fields: FieldOption[]) => (field: any) => {
  const query = field.query('query').get('value') || {};
  const selectedFields = getSelectedFields(fields, query);
  field.dataSource = selectedFields;
};

export const useFormatters = (fields: FieldOption[]) => (field: any) => {
  const selectedField = field.query('.field').get('value');
  if (!selectedField) {
    field.dataSource = [];
    return;
  }
  let options = [];
  const fieldInterface = getField(fields, selectedField)?.interface;
  switch (fieldInterface) {
    case 'datetime':
    case 'datetimeTz':
    case 'unixTimestamp':
    case 'datetimeNoTz':
    case 'createdAt':
    case 'updatedAt':
      options = formatters.datetime;
      break;
    case 'date':
      options = formatters.date;
      break;
    case 'time':
      options = formatters.time;
      break;
    default:
      options = [];
  }
  field.dataSource = options;
};

export const useCollectionOptions = () => {
  const { t } = useTranslation();
  const dm = useDataSourceManager();
  const { parseAction } = useACLRoleContext();
  const allCollections = dm.getAllCollections({
    filterCollection: (collection) => {
      const params = parseAction(`${collection.name}:list`);
      return params;
    },
  });
  const options = allCollections
    .filter(({ key, isDBInstance }) => key === DEFAULT_DATA_SOURCE_KEY || isDBInstance)
    .map(({ key, displayName, collections }) => ({
      value: key,
      label: displayName,
      children: collections.map((collection) => ({
        value: collection.name,
        label: collection.title,
      })),
    }));
  return useMemo(() => Schema.compile(options, { t }), [options]);
};

export const useOrderFieldsOptions = (defaultOptions: any[], fields: FieldOption[]) => (field: any) => {
  const query = field.query('query').get('value') || {};
  const { measures = [] } = query;
  const hasAgg = measures.some((measure: { aggregation?: string }) => measure.aggregation);
  if (!hasAgg) {
    field.componentProps.fieldNames = {
      label: 'title',
      value: 'name',
      children: 'children',
    };
    field.dataSource = defaultOptions;
    return;
  }
  const selectedFields = getSelectedFields(fields, query);
  field.componentProps.fieldNames = {};
  field.dataSource = selectedFields;
};

export const useOrderReaction = (defaultOptions: any[], fields: FieldOption[]) => (field: ArrayField) => {
  const query = field.query('query').get('value') || {};
  const { measures = [] } = query;
  const hasAgg = measures.some((measure: { aggregation?: string }) => measure.aggregation);
  let dataSource = defaultOptions;
  const allValues = [];
  if (hasAgg) {
    dataSource = getSelectedFields(fields, query);
    dataSource.forEach((field) => {
      allValues.push(field.value);
    });
  } else {
    dataSource.forEach((field) => {
      const children = field.children || [];
      if (!children.length) {
        allValues.push(field.value || field.name);
      }
      children.forEach((child: any) => {
        allValues.push(`${field.name}.${child.name}`);
      });
    });
  }

  const orders = field.value || [];
  const newOrders = orders.reduce((newOrders: any[], item: any) => {
    const { alias } = parseField(item.field);
    if (!item.field || allValues.includes(alias)) {
      newOrders.push(item);
    }
    return newOrders;
  }, []);
  field.setValue(newOrders);
};

export const useData = (data?: any[], dataSource?: string, collection?: string) => {
  const { t } = useChartsTranslation();
  const { service, query } = useContext(ChartRendererContext);
  const fields = useFieldsWithAssociation(dataSource, collection);
  const form = useForm();
  const selectedFields = getSelectedFields(fields, form?.values?.query || query);
  return processData(selectedFields, service?.data || data || [], { t });
};

export const useCollectionFieldsOptions = (dataSource: string, collectionName: string, maxDepth = 2, excludes = []) => {
  const { cm, fim, collection } = useChartDataSource(dataSource);
  const collectionFields = cm.getCollectionFields(collectionName || collection);
  const fields = collectionFields.filter((v) => !excludes.includes(v.interface));

  const field2option = (field, depth, prefix?) => {
    if (!field.interface || field.isForeignKey) {
      return;
    }
    const fieldInterface = fim.getFieldInterface(field.interface);
    if (!fieldInterface?.filterable) {
      return;
    }
    const { nested, children } = fieldInterface.filterable;
    const value = prefix ? `${prefix}.${field.name}` : field.name;
    const option = {
      ...field,
      name: field.name,
      title: field?.uiSchema?.title || field.name,
      schema: field?.uiSchema,
      key: value,
    };
    if (field.target && depth > maxDepth) {
      return;
    }
    if (depth > maxDepth) {
      return option;
    }
    if (children?.length) {
      option['children'] = children.map((v) => {
        return {
          ...v,
          key: `${field.name}.${v.name}`,
        };
      });
    }
    if (nested) {
      const targetFields = cm.getCollectionFields(field.target).filter((v) => !excludes.includes(v.interface));
      const options = getOptions(targetFields, depth + 1, field.name).filter(Boolean);
      option['children'] = option['children'] || [];
      option['children'].push(...options);
    }
    return option;
  };
  const getOptions = (fields, depth, prefix?) => {
    const options = [];
    fields.forEach((field) => {
      const option = field2option(field, depth, prefix);
      if (option) {
        options.push(option);
      }
    });
    return options;
  };
  const options = getOptions(fields, 1);
  return options;
};

export const useCollectionFilterOptions = (dataSource: string, collection: string) => {
  const { cm, fim, collection: _collection } = useChartDataSource(dataSource);
  return useMemo(() => {
    const fields = cm.getCollectionFields(collection || _collection);
    const field2option = (field, depth) => {
      if (!field.interface || field.isForeignKey) {
        return;
      }
      const fieldInterface = fim.getFieldInterface(field.interface);
      if (!fieldInterface?.filterable) {
        return;
      }
      const { nested, children, operators } = fieldInterface.filterable;
      const option = {
        name: field.name,
        title: field?.uiSchema?.title || field.name,
        schema: field?.uiSchema,
        operators:
          operators?.filter?.((operator) => {
            return !operator?.visible || operator.visible(field);
          }) || [],
        interface: field.interface,
      };
      if (field.target && depth > 2) {
        return;
      }
      if (depth > 2) {
        return option;
      }
      if (children?.length) {
        option['children'] = children;
      }
      if (nested) {
        const targetFields = cm.getCollectionFields(field.target);
        const options = getOptions(targetFields, depth + 1).filter(Boolean);
        option['children'] = option['children'] || [];
        option['children'].push(...options);
      }
      return option;
    };
    const getOptions = (fields, depth) => {
      const options = [];
      fields.forEach((field) => {
        const option = field2option(field, depth);
        if (option) {
          options.push(option);
        }
      });
      return options;
    };
    const options = getOptions(fields, 1);
    return options;
  }, [collection, cm, fim]);
};
