import type React from 'react';
import {
  CRM_BAR_CHART_WIDGET_TYPE,
  CRM_DASHBOARD_WIDGET_PALETTE,
  CRM_DASHBOARD_WIDGET_TOOLBAR_ACTIONS,
  CRM_DASHBOARD_WIDGET_TYPES,
  CRM_DEFAULT_DASHBOARD_WIDGET_TYPE,
  CRM_FUNNEL_CHART_WIDGET_TYPE,
  CRM_GAUGE_CHART_WIDGET_TYPE,
  CRM_ICICLE_CHART_WIDGET_TYPE,
  CRM_LINE_CHART_WIDGET_TYPE,
  CRM_METRIC_WIDGET_TYPE,
  CRM_PIE_CHART_WIDGET_TYPE,
  CRM_REPORT_COMPONENT_TYPE,
  CRM_REPORT_WIDGET_TYPE,
  getCrmDashboardWidgetApiMapping,
  getCrmDashboardWidgetSize,
  isCrmDashboardChartWidgetType,
  isCrmImageDashboardWidgetType,
  isCrmTextDashboardWidgetType,
  type CrmDashboardWidgetApiMapping,
  type CrmDashboardWidgetPaletteGroup,
  type CrmDashboardWidgetSize,
  type CrmDashboardWidgetToolbarAction,
} from './dashboard-widget-types';
import {
  CRM_METADATA_FIELD_TYPE_DEFINITIONS,
  CRM_METADATA_OPERATOR_DEFINITIONS,
  type CrmMetadataFieldOperatorId,
  type CrmMetadataFieldTypeDefinition,
  type CrmMetadataFieldTypeId,
} from './metadata-field-type-policy';
import { CRM_FRONTEND_APP_ID } from './route-surface-policy';
import {
  CRM_SETUP_CHILD_ROUTES,
  CRM_SETUP_COMPONENTS,
  CRM_SETUP_PAGES,
  type CrmSetupChildRouteDefinition,
  type CrmSetupComponentName,
  type CrmSetupPageDefinition,
} from './setup-navigation-policy';
import {
  CRM_CURRENCY_CODE_FIELD,
  CRM_FIELD_DISPLAY_VALUE_SUFFIX,
  CRM_LOOKUP_FALLBACK_NAME_SUFFIXES,
  CRM_LOOKUP_NAME_SUFFIX,
  CRM_LOOKUP_TYPE_SUFFIX,
  CRM_MULTIPICKLIST_DELIMITER,
  CRM_UI_FIELD_INPUT_TYPES,
  CRM_UI_FIELD_RENDERER_TYPES,
  CRM_URL_VALUE_SUFFIX,
} from './ui-field-surface-policy';
import {
  createCrmUiFieldComponents,
  type CrmUiFieldHost,
  type CrmUiFieldInputProps,
  type CrmUiFieldRendererProps,
} from './ui-field-components';

export interface CrmDashboardWidgetPolicy {
  defaultType: string;
  widgetTypes: readonly string[];
  palette: readonly CrmDashboardWidgetPaletteGroup[];
  toolbarActions: readonly CrmDashboardWidgetToolbarAction[];
  sizes: Readonly<Record<string, CrmDashboardWidgetSize>>;
  apiMappings: Readonly<Record<string, CrmDashboardWidgetApiMapping>>;
  chartWidgetTypes: readonly string[];
  textWidgetTypes: readonly string[];
  imageWidgetTypes: readonly string[];
  metricWidgetTypes: readonly string[];
  pieChartWidgetTypes: readonly string[];
  reportSourceWidgetTypes: readonly string[];
}

export interface CrmDashboardWidgetRegistryHost<TWidget> {
  registerPolicy: (policy: CrmDashboardWidgetPolicy) => void;
  registerWidget: (type: string, widget: TWidget) => void;
}

export interface CrmDashboardWidgetComponents<TWidget> {
  MetricWidget: TWidget;
  ChartWidget: TWidget;
  FunnelWidget: TWidget;
  GaugeWidget: TWidget;
  IcicleWidget: TWidget;
}

export interface CrmDashboardWidgetContributionHost<TWidget> {
  registry: CrmDashboardWidgetRegistryHost<TWidget>;
  widgets: CrmDashboardWidgetComponents<TWidget>;
}

const crmDashboardWidgetPolicyTypeSet = new Set<string>(CRM_DASHBOARD_WIDGET_TYPES);

export function createCrmDashboardWidgetPolicy(): CrmDashboardWidgetPolicy {
  return {
    defaultType: CRM_DEFAULT_DASHBOARD_WIDGET_TYPE,
    widgetTypes: CRM_DASHBOARD_WIDGET_TYPES,
    palette: CRM_DASHBOARD_WIDGET_PALETTE
      .map(group => ({
        ...group,
        items: group.items.filter(item => crmDashboardWidgetPolicyTypeSet.has(item.type)),
      }))
      .filter(group => group.items.length > 0),
    toolbarActions: CRM_DASHBOARD_WIDGET_TOOLBAR_ACTIONS.filter(action => (
      crmDashboardWidgetPolicyTypeSet.has(action.type)
    )),
    sizes: Object.fromEntries(CRM_DASHBOARD_WIDGET_TYPES.map(type => [type, getCrmDashboardWidgetSize(type)])),
    apiMappings: Object.fromEntries(CRM_DASHBOARD_WIDGET_TYPES.map(type => [type, getCrmDashboardWidgetApiMapping(type)])),
    chartWidgetTypes: CRM_DASHBOARD_WIDGET_TYPES.filter(isCrmDashboardChartWidgetType),
    textWidgetTypes: CRM_DASHBOARD_WIDGET_TYPES.filter(isCrmTextDashboardWidgetType),
    imageWidgetTypes: CRM_DASHBOARD_WIDGET_TYPES.filter(isCrmImageDashboardWidgetType),
    metricWidgetTypes: [CRM_METRIC_WIDGET_TYPE],
    pieChartWidgetTypes: [CRM_PIE_CHART_WIDGET_TYPE],
    reportSourceWidgetTypes: CRM_DASHBOARD_WIDGET_TYPES.filter(type => (
      getCrmDashboardWidgetApiMapping(type).componentType === CRM_REPORT_COMPONENT_TYPE
    )),
  };
}

export function registerCrmDashboardWidgetContribution<TWidget>(
  host: CrmDashboardWidgetContributionHost<TWidget>,
): void {
  const { registry, widgets } = host;
  registry.registerPolicy(createCrmDashboardWidgetPolicy());
  registry.registerWidget(CRM_METRIC_WIDGET_TYPE, widgets.MetricWidget);
  registry.registerWidget(CRM_BAR_CHART_WIDGET_TYPE, widgets.ChartWidget);
  registry.registerWidget(CRM_LINE_CHART_WIDGET_TYPE, widgets.ChartWidget);
  registry.registerWidget(CRM_PIE_CHART_WIDGET_TYPE, widgets.ChartWidget);
  registry.registerWidget(CRM_FUNNEL_CHART_WIDGET_TYPE, widgets.FunnelWidget);
  registry.registerWidget(CRM_GAUGE_CHART_WIDGET_TYPE, widgets.GaugeWidget);
  registry.registerWidget(CRM_ICICLE_CHART_WIDGET_TYPE, widgets.IcicleWidget);
  registry.registerWidget(CRM_REPORT_WIDGET_TYPE, widgets.ChartWidget);
}

export interface CrmPlatformFieldTypeDefinition {
  sqlType?: string;
}

export interface CrmRuntimeMetadataFieldTypeDefinition<
  TFieldType extends string = string,
  TOperator extends string = string,
> {
  type: TFieldType;
  label: string;
  sqlType: string;
  icon: string;
  description: string;
  isSystemOnly?: boolean;
  isSearchable?: boolean;
  isGroupable?: boolean;
  isSummable?: boolean;
  isVirtual?: boolean;
  operators: TOperator[];
}

export interface CrmMetadataContributionHost<
  TFieldType extends string = string,
  TOperator extends string = string,
> {
  platformFieldTypes: Partial<Record<CrmMetadataFieldTypeId, CrmPlatformFieldTypeDefinition>>;
  operatorSymbols?: Partial<Record<CrmMetadataFieldOperatorId, TOperator>>;
  registerFieldTypes: (definitions: Array<CrmRuntimeMetadataFieldTypeDefinition<TFieldType, TOperator>>) => void;
}

function crmDefaultOperatorSymbol(operator: CrmMetadataFieldOperatorId): string | undefined {
  if (operator in CRM_METADATA_OPERATOR_DEFINITIONS) {
    return CRM_METADATA_OPERATOR_DEFINITIONS[operator as keyof typeof CRM_METADATA_OPERATOR_DEFINITIONS].symbol;
  }
  return undefined;
}

function crmOperatorSymbol<TOperator extends string>(
  operator: CrmMetadataFieldOperatorId,
  host: Pick<CrmMetadataContributionHost<string, TOperator>, 'operatorSymbols'>,
): TOperator | undefined {
  return host.operatorSymbols?.[operator] ?? (crmDefaultOperatorSymbol(operator) as TOperator | undefined);
}

function createCrmMetadataDefinition<
  TFieldType extends string,
  TOperator extends string,
>(
  type: CrmMetadataFieldTypeId,
  definition: CrmMetadataFieldTypeDefinition,
  host: CrmMetadataContributionHost<TFieldType, TOperator>,
): CrmRuntimeMetadataFieldTypeDefinition<TFieldType, TOperator> {
  const operators = definition.operators
    .map(op => crmOperatorSymbol(op, host))
    .filter((op): op is TOperator => !!op);

  return {
    type: type as TFieldType,
    label: definition.label,
    sqlType: host.platformFieldTypes[type]?.sqlType ?? '',
    icon: definition.icon,
    description: definition.description,
    isSystemOnly: definition.isSystemOnly,
    isSearchable: definition.isSearchable,
    isGroupable: definition.isGroupable,
    isSummable: definition.isSummable,
    isVirtual: definition.isVirtual,
    operators,
  };
}

export function createCrmMetadataFieldTypeDefinitions<
  TFieldType extends string = string,
  TOperator extends string = string,
>(
  host: CrmMetadataContributionHost<TFieldType, TOperator>,
): Array<CrmRuntimeMetadataFieldTypeDefinition<TFieldType, TOperator>> {
  return Object.entries(CRM_METADATA_FIELD_TYPE_DEFINITIONS).map(([type, definition]) => (
    createCrmMetadataDefinition(type as CrmMetadataFieldTypeId, definition, host)
  ));
}

export function registerCrmMetadataContribution<
  TFieldType extends string = string,
  TOperator extends string = string,
>(host: CrmMetadataContributionHost<TFieldType, TOperator>): void {
  host.registerFieldTypes(createCrmMetadataFieldTypeDefinitions(host));
}

export type CrmRuntimeSetupPageDefinition = Omit<CrmSetupPageDefinition, 'componentName'> & {
  component_name: CrmSetupComponentName;
};

export interface CrmSetupContributionHost<TComponent> {
  components: Record<CrmSetupComponentName, TComponent>;
  registerComponent: (name: string, component: TComponent) => void;
  registerPages: (appId: string, pages: CrmRuntimeSetupPageDefinition[]) => void;
  registerChildRoutes: (appId: string, routes: readonly CrmSetupChildRouteDefinition[]) => void;
}

export function createCrmSetupRuntimePages(): CrmRuntimeSetupPageDefinition[] {
  return CRM_SETUP_PAGES.map(({ componentName, ...page }) => ({
    ...page,
    component_name: componentName,
  }));
}

export function registerCrmSetupContribution<TComponent>(
  host: CrmSetupContributionHost<TComponent>,
): void {
  CRM_SETUP_COMPONENTS.forEach(({ name }) => {
    const component = host.components[name];
    if (!component) {
      throw new Error(`CRM setup component "${name}" is declared by source but not bound by the runtime adapter`);
    }
    host.registerComponent(name, component);
  });
  host.registerPages(CRM_FRONTEND_APP_ID, createCrmSetupRuntimePages());
  host.registerChildRoutes(CRM_FRONTEND_APP_ID, CRM_SETUP_CHILD_ROUTES);
}

export interface CrmUiFieldSurfacePolicy {
  displayValueSuffix: string;
  lookupNameSuffix: string;
  lookupTypeSuffix: string;
  lookupFallbackNameSuffixes: readonly string[];
  urlValueSuffix: string;
  currencyCodeField: string;
  multipicklistDelimiter: string;
}

export interface CrmUiFieldContributionHost<
  TRenderer extends React.FC<CrmUiFieldRendererProps> = React.FC<CrmUiFieldRendererProps>,
  TInput extends React.FC<CrmUiFieldInputProps> = React.FC<CrmUiFieldInputProps>,
> extends CrmUiFieldHost {
  registerFieldSurfacePolicy: (appId: string, policy: CrmUiFieldSurfacePolicy) => void;
  registerFieldRenderer: (type: string, renderer: TRenderer) => void;
  /** Installs a renderer for one (object, field) pair, ahead of the type-keyed one. */
  registerFieldRendererByName: (objectApiName: string, fieldApiName: string, renderer: TRenderer) => void;
  registerFieldInput: (type: string, input: TInput) => void;
}

export function createCrmUiFieldSurfacePolicy(): CrmUiFieldSurfacePolicy {
  return {
    displayValueSuffix: CRM_FIELD_DISPLAY_VALUE_SUFFIX,
    lookupNameSuffix: CRM_LOOKUP_NAME_SUFFIX,
    lookupTypeSuffix: CRM_LOOKUP_TYPE_SUFFIX,
    lookupFallbackNameSuffixes: CRM_LOOKUP_FALLBACK_NAME_SUFFIXES,
    urlValueSuffix: CRM_URL_VALUE_SUFFIX,
    currencyCodeField: CRM_CURRENCY_CODE_FIELD,
    multipicklistDelimiter: CRM_MULTIPICKLIST_DELIMITER,
  };
}

export function registerCrmUiFieldContribution<
  TRenderer extends React.FC<CrmUiFieldRendererProps> = React.FC<CrmUiFieldRendererProps>,
  TInput extends React.FC<CrmUiFieldInputProps> = React.FC<CrmUiFieldInputProps>,
>(host: CrmUiFieldContributionHost<TRenderer, TInput>): void {
  host.registerFieldSurfacePolicy(CRM_FRONTEND_APP_ID, createCrmUiFieldSurfacePolicy());
  const { fieldRenderers, fieldInputs, fieldRendererOverrides } = createCrmUiFieldComponents(host);

  CRM_UI_FIELD_RENDERER_TYPES.forEach((type) => {
    host.registerFieldRenderer(type, fieldRenderers[type] as TRenderer);
  });
  fieldRendererOverrides.forEach(({ objectApiName, fieldApiName, renderer }) => {
    host.registerFieldRendererByName(objectApiName, fieldApiName, renderer as TRenderer);
  });
  CRM_UI_FIELD_INPUT_TYPES.forEach((type) => {
    host.registerFieldInput(type, fieldInputs[type] as TInput);
  });
}
