import {CardRenderer} from "./CardRenderers";
import {ComponentAction, componentCategories, ComponentCategory, ComponentRuntimeData} from "./components";
import {CardType} from "./cards";
import {__, CouponsPlus, getRegisteredComponentsMeta} from "../globals";
import {FieldsMap} from "./fields";
import {FieldUpdateAction} from "../Fields";
import {SelectOption} from "./fields/MultiSelectSearch";
import {TierType} from "./tiers";
import {PopupWindowStateContext} from "./atoms";
import {ExportImportTarget} from "./export/Exporters";
import {ReactNode} from "react";
import {sortBy} from "lodash";

// Dynamic import of all meta components from the cards directory using Vite
// @ts-ignore
const modules = import.meta.glob(['./cards/*.tsx', './cards/conditions/*.tsx', "./cards/stubs/*.tsx"], {eager: true});

// Initialize ComponentsMeta structure
export const ComponentsMeta: Record<ComponentType, ComponentMeta[]> = {
    filters: [],
    conditions: [],
    offers: []
};

// Process all modules and add meta components to appropriate arrays
const addComponentsMeta = (meta: ComponentMeta) => {
    if (meta.type && ComponentsMeta[meta.type]) {
        ComponentsMeta[meta.type].push(meta);
    }
}

Object.values(modules).forEach((module: any) => {
    // Find meta objects in the module (assuming they end with "Meta")
    Object.entries(module).forEach(([key, value]) => {
        if (key.endsWith('Meta') && typeof value === 'object' && value !== null) {
            addComponentsMeta(value as ComponentMeta);
        }
    });
});
/**
 * Add the external components
 */
for (const externalComponentType in getRegisteredComponentsMeta()) {
    const externalComponentsMeta = getRegisteredComponentsMeta()[externalComponentType as ComponentType] || [];

    externalComponentsMeta.forEach((componentMeta: ComponentMeta) => {
        addComponentsMeta(componentMeta);
    });
}

export type CategorizedComponents = Partial<Record<ComponentCategory, ComponentMeta[]>>;

export const getComponentsOrderedByCategory = (type: ComponentType, supported: string[], unsupported: string[], context?: 'tier-base', withImplementation: boolean = true): CategorizedComponents => {
    const components = (ComponentsMeta[type])
    const orderedComponents: CategorizedComponents = {}

    const otherComponents: ComponentMeta[] = []
    components.forEach((componentMeta) => {
        const isImplemented = hasImplementation(componentMeta)

        // if we only want implemented components
        if (withImplementation) {
            if (!isImplemented) {
                // skip unimplemented components
                return;
            }
        } else {
            // if we only want unimplemented components
            if (hasImplementation(componentMeta)) {
                // skip implemented components
                return;
            }
        }

        let {id, category} = componentMeta;
        let isSupported = true;

        if (supported.length) {
            isSupported = supported.includes(id)
        } else if (unsupported.length) {
            isSupported = !unsupported.includes(id)
        }


        if (!isSupported) {
            // skip unsupported components
            return;
        }
        if (context?.startsWith('tier-base') && componentMeta.supportsTiers !== true) {
            return
        }

        if (!isImplemented) {
            category = ComponentCategory.Pro
        }

        if (!category || category === ComponentCategory.Other) {
            // let's ad them here so that 'other' is always at the end
            otherComponents.push(componentMeta)
        } else {
            if (!orderedComponents[category]) {
                orderedComponents[category] = [];
            }

            orderedComponents[category]!.push(componentMeta)
        }

    })



    if (otherComponents.length > 0) {
        orderedComponents[ComponentCategory.Other] = otherComponents;
    }

    // sort items in each category. done here for clarity and because sorting at once at the beginning of this function changes
    // th order of the groups to and even though there's better ways to do this i dont want to spend to much time doing this so this'll have to do.
    for (let category in orderedComponents) {
        orderedComponents[category] = sortComponents(orderedComponents[category]);
    }

    // now sort the categories theyselves
    const categoriesAndPriorities: [string, number][] = []

    for (let category in orderedComponents) {
        categoriesAndPriorities.push([category, componentCategories[category].priority])
    }

    const orderedCategories: CategorizedComponents = {}

    ;[...categoriesAndPriorities]
        .sort(([, priorityA], [, priorityB]) => (priorityA ?? 50) - (priorityB ?? 50))
        .map(([name]) => name)
        .forEach(name => {
            orderedCategories[name] = orderedComponents[name]
        })

    return orderedCategories
}

const sortComponents = (componentsMetaElement: ComponentMeta[]): ComponentMeta[] => {
    // it don't matter if we change the reference here, matter of fact im going to add a flag so that this only happens once
    // i don't know how to get a reference here though and im on my way now so no internet connection so tha will have to wait
    // I forgot about this didn't I? it's not really that big a deal anyway
    return componentsMetaElement.sort((a, b) => (a.priority ?? 50) - (b.priority ?? 50))
};

export type ComponentType = 'filters' | 'conditions' | 'offers';

const getUnavailableComponentData = (type: string): CardType => ({
    type,
    name: __('Unavailable component'),
    description: __('This component is currently unavailable.'),
    useCases: [],
    notes: [],
    fields: {},
    defaultOptions: {},
    supports: {
        recursiveness: false,
    }
})

export const getComponentMetaData = (componentMeta: ComponentMeta | [ComponentType, string]): CardType => {
    const unresolvedType = Array.isArray(componentMeta) ? componentMeta[1] : componentMeta.id
    componentMeta = resolveComponentMeta(componentMeta);

    if (!componentMeta) {
        return getUnavailableComponentData(unresolvedType)
    }

    return CouponsPlus.components[componentMeta.type]?.[componentMeta.id]
        ?? CouponsPlus.stubs?.[componentMeta.type]?.[componentMeta.id]
        ?? getUnavailableComponentData(componentMeta.id);
}

export const getComponentMetaDataField = (componentMeta: ComponentMeta | [ComponentType, string], field: string) => {
    const resolvedMeta = resolveComponentMeta(componentMeta)
    const clientField = resolvedMeta?.[field as keyof ComponentMeta];

    if (clientField) {
        return clientField as ComponentMeta[typeof field];
    }

    return getComponentMetaData(resolvedMeta || componentMeta)[field as keyof CardType];
}

const resolveComponentMeta = (componentMeta: ComponentMeta | [ComponentType, string]): ComponentMeta | undefined => {
    if (Array.isArray(componentMeta)) {
        const [parentType, type] = componentMeta;
        componentMeta = ComponentsMeta[parentType].find(meta => meta.id === type) as ComponentMeta | undefined
    }

    return componentMeta;
}

export type ComponentMeta = {
    id: string,
    type: ComponentType,
    icon?: CardRenderer['icon'],
    listTitle?: (preRenderedTitle: ReactNode, name: string) => ReactNode,
    relatedSearchTerms?: string[],
    //Title?: ({suggestedScope}: {suggestedScope: string}) => string | ReactNode,
    description?: ({suggestedScope}: {suggestedScope: string}) => string | ReactNode,
    supportsCustomTitleForSuggestedScopes?: (suggestedScope: string) =>  boolean, // for example, products.tsx supports custom titles for bogos and item_discounts. OnSale does not support custom title on item_discounts scope.
    fieldsTitlePrefix?: false | undefined, // false to disable the prefix, undefined to use the default prefix
    fieldHooks?: Record<
        // the id of the field
        string,
        Partial<{
            [FieldUpdateAction.Add]: <ReturnType>(id: string, value: SelectOption<any>) => any;
        }>
    >,
    fieldsMap?: FieldsMap | ((componentRuntimeData: ComponentRuntimeData, scope: PopupWindowStateContext<any>['scope'], suggestedScope?: string) => FieldsMap),
    fieldsNotes?: string[] | ((componentRuntimeData: ComponentRuntimeData, scope: PopupWindowStateContext<any>['scope']) => string[]), // for extra notes at the bottom that depend on runtime data
    canShowBottomNavigation?: (componentRuntimeData: ComponentRuntimeData) => boolean
    prename?: (componentData: ComponentRuntimeData[]) => string,
    titlePrefix?: ReactNode,
    action?: (componentData: ComponentRuntimeData[]) => ComponentAction,
    category?: string,
    supportsTiers?: boolean, // whether this component can be used inside a tier or not. Default is false
    tierType?: TierType,
    tierFields?: string[], // (REQUIRED IF supportsTiers) IMPORTANT: these are the fields that are ONLY supported by testable partials, in dot notation, eg: ['amount', 'product.categories', etc.] IT IS VERY IMPORTANT THAT THESE FIELDS ARE DEFINED OTHERWISE USING THE WHOLE FIELDS WHEN THE BASE TESTABLE CAN BE CONFIGURED SEPARATELY WILL LEAD TO BUGS.
    tierDefaultOptions?: {
        first: () => Record<string, any>,
        // probbaly here on new tier
    }
    configurable?: undefined | false | ((context: string | 'tier-base') => boolean), // if undefined, it means the component is configurable. Default is undefined (configurable)
    incompatibleWith?: string[], // these components cannot be used together with this one
    export?: (cardData: any, exportTarget: ExportImportTarget) => any, // used when exporting the rules to a specific format (eg: coupon conditions to woocommerce format)
    priority?: number, //10 based, default is 50
}

export const getComponentMeta = (type: ComponentType, id: string): ComponentMeta | undefined => {
    return ComponentsMeta[type].find(meta => meta.id === id);
}

/**
 * 'type' here refers to the individual component type, eg: UserRole, Categories, etc
 */
export const getComponentMetaFromType = (type: string): typeof ComponentsMeta[ComponentType][number] | undefined => {
    for (const category in ComponentsMeta) {
        const metas = ComponentsMeta[category];

        const meta = metas?.find(m => m.id === type);

        if (meta) {
            return meta;
        }
    }

    return undefined;
}

export const hasImplementation = (componentMeta: ComponentMeta | undefined): boolean => {
    if (!componentMeta) {
        return false
    }
    return CouponsPlus.components[componentMeta.type][componentMeta.id] !== undefined
}
