export type WiredVariablePickerTarget = 'user' | 'furni' | 'global' | 'context';
export type WiredVariablePickerUsage = 'give' | 'remove' | 'change-destination' | 'change-reference' | 'condition' | 'filter-main' | 'echo';

export interface IWiredVariableDefinitionLike
{
    availability: number;
    hasValue: boolean;
    isReadOnly?: boolean;
    itemId: number;
    name: string;
}

export interface IWiredVariablePickerEntry
{
    id: string;
    token: string;
    label: string;
    displayLabel: string;
    searchableText: string;
    selectable: boolean;
    hasValue: boolean;
    kind: 'internal' | 'custom';
    target: WiredVariablePickerTarget;
    children?: IWiredVariablePickerEntry[];
}

interface IInternalVariableMeta
{
    key: string;
    canUseAsDestination: boolean;
    canUseAsReference: boolean;
}

const INTERNAL_VARIABLE_ALIASES: Record<string, string> = {
    '@position.x': '@position_x',
    '@position.y': '@position_y',
    '@effect': '@effect_id',
    '@handitems': '@handitem_id',
    '@is_mute': '@is_muted',
    '@teams.red.score': '@team_red_score',
    '@teams.green.score': '@team_green_score',
    '@teams.blue.score': '@team_blue_score',
    '@teams.yellow.score': '@team_yellow_score',
    '@teams.red.size': '@team_red_size',
    '@teams.green.size': '@team_green_size',
    '@teams.blue.size': '@team_blue_size',
    '@teams.yellow.size': '@team_yellow_size'
};

const CUSTOM_TOKEN_PREFIX = 'custom:';
const INTERNAL_TOKEN_PREFIX = 'internal:';
const GROUP_TOKEN_PREFIX = 'group:';

const createInternalMeta = (key: string, canUseAsDestination = false, canUseAsReference = false): IInternalVariableMeta =>
    ({
        key,
        canUseAsDestination,
        canUseAsReference
    });

export const normalizeInternalVariableKey = (key: string) =>
{
    const normalizedKey = key?.trim();

    if(!normalizedKey) return '';

    return (INTERNAL_VARIABLE_ALIASES[normalizedKey] || normalizedKey);
};

const INTERNAL_VARIABLES: Record<'user' | 'furni' | 'global' | 'context', IInternalVariableMeta[]> = {
    furni: [
        createInternalMeta('~teleport.target_id', false, true),
        createInternalMeta('@id', false, true),
        createInternalMeta('@class_id', false, true),
        createInternalMeta('@height', false, true),
        createInternalMeta('@state', true, true),
        createInternalMeta('@position_x', true, true),
        createInternalMeta('@position_y', true, true),
        createInternalMeta('@rotation', true, true),
        createInternalMeta('@altitude', true, true),
        createInternalMeta('@is_invisible', false, true),
        createInternalMeta('@type', false, true),
        createInternalMeta('@is_stackable', false, true),
        createInternalMeta('@can_stand_on', false, true),
        createInternalMeta('@can_sit_on', false, true),
        createInternalMeta('@can_lay_on', false, true),
        createInternalMeta('@wallitem_offset', false, true),
        createInternalMeta('@dimensions.x', false, true),
        createInternalMeta('@dimensions.y', false, true),
        createInternalMeta('@owner_id', false, true)
    ],
    user: [
        createInternalMeta('@index', false, true),
        createInternalMeta('@type', false, true),
        createInternalMeta('@gender', false, true),
        createInternalMeta('@level', false, true),
        createInternalMeta('@achievement_score', false, true),
        createInternalMeta('@is_hc', false, true),
        createInternalMeta('@has_rights', false, true),
        createInternalMeta('@is_group_admin', false, true),
        createInternalMeta('@is_owner', false, true),
        createInternalMeta('@is_muted', false, true),
        createInternalMeta('@is_trading', false, true),
        createInternalMeta('@is_frozen', false, true),
        createInternalMeta('@effect_id', false, true),
        createInternalMeta('@team_score', false, true),
        createInternalMeta('@team_color', false, true),
        createInternalMeta('@team_type', false, true),
        createInternalMeta('@sign', false, true),
        createInternalMeta('@dance', false, true),
        createInternalMeta('@is_idle', false, true),
        createInternalMeta('@handitem_id', false, true),
        createInternalMeta('@position_x', true, true),
        createInternalMeta('@position_y', true, true),
        createInternalMeta('@direction', true, true),
        createInternalMeta('@altitude', false, true),
        createInternalMeta('@favourite_group_id', false, true),
        createInternalMeta('@room_entry.method', false, true),
        createInternalMeta('@room_entry.teleport_id', false, true),
        createInternalMeta('@user_id', false, true),
        createInternalMeta('@bot_id', false, true),
        createInternalMeta('@pet_id', false, true),
        createInternalMeta('@pet_owner_id', false, true)
    ],
    global: [
        createInternalMeta('@furni_count', false, true),
        createInternalMeta('@user_count', false, true),
        createInternalMeta('@wired_timer', false, true),
        createInternalMeta('@team_red_score', false, true),
        createInternalMeta('@team_green_score', false, true),
        createInternalMeta('@team_blue_score', false, true),
        createInternalMeta('@team_yellow_score', false, true),
        createInternalMeta('@team_red_size', false, true),
        createInternalMeta('@team_green_size', false, true),
        createInternalMeta('@team_blue_size', false, true),
        createInternalMeta('@team_yellow_size', false, true),
        createInternalMeta('@room_id', false, true),
        createInternalMeta('@group_id', false, true),
        createInternalMeta('@timezone_server', false, true),
        createInternalMeta('@timezone_client', false, true),
        createInternalMeta('@current_time', false, true),
        createInternalMeta('@current_time.millisecond_of_second', false, true),
        createInternalMeta('@current_time.seconds_of_minute', false, true),
        createInternalMeta('@current_time.minute_of_hour', false, true),
        createInternalMeta('@current_time.hour_of_day', false, true),
        createInternalMeta('@current_time.day_of_week', false, true),
        createInternalMeta('@current_time.day_of_month', false, true),
        createInternalMeta('@current_time.day_of_year', false, true),
        createInternalMeta('@current_time.week_of_year', false, true),
        createInternalMeta('@current_time.month_of_year', false, true),
        createInternalMeta('@current_time.year', false, true)
    ],
    context: [
        createInternalMeta('@selector_furni_count', false, true),
        createInternalMeta('@selector_user_count', false, true),
        createInternalMeta('@signal_furni_count', false, true),
        createInternalMeta('@signal_user_count', false, true),
        createInternalMeta('@antenna_id', false, true),
        createInternalMeta('@chat_type', false, true),
        createInternalMeta('@chat_style', false, true)
    ]
};

const sortEntries = (left: IWiredVariablePickerEntry, right: IWiredVariablePickerEntry) =>
{
    return left.displayLabel.localeCompare(right.displayLabel, undefined, { sensitivity: 'base' });
};

const getNormalizedInternalTarget = (target: WiredVariablePickerTarget): 'user' | 'furni' | 'global' | 'context' =>
{
    if(target === 'furni') return 'furni';
    if(target === 'user') return 'user';
    if(target === 'context') return 'context';

    return 'global';
};

const getInternalSelectable = (usage: WiredVariablePickerUsage, meta: IInternalVariableMeta) =>
{
    switch(usage)
    {
        case 'condition': return true;
        case 'filter-main': return meta.canUseAsReference;
        case 'echo': return true;
        case 'change-destination': return meta.canUseAsDestination;
        case 'change-reference': return meta.canUseAsReference;
        default: return false;
    }
};

const getCustomSelectable = (usage: WiredVariablePickerUsage, definition: IWiredVariableDefinitionLike) =>
{
    switch(usage)
    {
        case 'condition':
        case 'filter-main':
            return true;
        case 'echo':
            return definition.name.includes('.');
        case 'change-reference':
            return !!definition.hasValue;
        case 'change-destination':
            return (!!definition.hasValue && !definition.isReadOnly);
        default:
            return !definition.isReadOnly;
    }
};

const getRootKey = (key: string) =>
{
    const separatorIndex = key.indexOf('.');

    if(separatorIndex < 0) return null;

    return key.slice(0, separatorIndex);
};

const createInternalEntry = (target: WiredVariablePickerTarget, usage: WiredVariablePickerUsage, meta: IInternalVariableMeta): IWiredVariablePickerEntry =>
    ({
        id: `${ INTERNAL_TOKEN_PREFIX }${ meta.key }`,
        token: `${ INTERNAL_TOKEN_PREFIX }${ meta.key }`,
        label: meta.key,
        displayLabel: meta.key,
        searchableText: meta.key,
        selectable: getInternalSelectable(usage, meta),
        hasValue: meta.canUseAsReference,
        kind: 'internal',
        target
    });

const createCustomEntry = (target: WiredVariablePickerTarget, usage: WiredVariablePickerUsage, definition: IWiredVariableDefinitionLike): IWiredVariablePickerEntry =>
    ({
        id: `${ CUSTOM_TOKEN_PREFIX }${ definition.itemId }`,
        token: `${ CUSTOM_TOKEN_PREFIX }${ definition.itemId }`,
        label: definition.name,
        displayLabel: definition.name,
        searchableText: definition.name,
        selectable: getCustomSelectable(usage, definition),
        hasValue: !!definition.hasValue,
        kind: 'custom',
        target
    });

const groupEntries = (entries: IWiredVariablePickerEntry[]) =>
{
    const groupedParents = new Map<string, { exact?: IWiredVariablePickerEntry; children: IWiredVariablePickerEntry[]; }>();

    for(const entry of entries)
    {
        const displayLabel = entry.displayLabel?.trim();

        if(!displayLabel?.length)
        {
            continue;
        }

        const rootKey = getRootKey(displayLabel) || displayLabel;
        let group = groupedParents.get(rootKey);

        if(!group)
        {
            group = { children: [] };
            groupedParents.set(rootKey, group);
        }

        if(displayLabel === rootKey)
        {
            group.exact = {
                ...entry,
                label: displayLabel,
                displayLabel,
                searchableText: displayLabel
            };
            continue;
        }

        const childLabel = displayLabel.slice(rootKey.length + 1).trim();

        if(!childLabel.length) continue;

        group.children.push({
            ...entry,
            label: childLabel,
            displayLabel,
            searchableText: `${ displayLabel } ${ childLabel }`
        });
    }

    const groupedEntries: IWiredVariablePickerEntry[] = [];

    for(const [ rootKey, group ] of groupedParents)
    {
        const sortedChildren = [ ...group.children ]
            .sort(sortEntries)
            .filter((child, index, collection) => collection.findIndex(entry => (entry.token === child.token)) === index);
        const shouldGroup = !!sortedChildren.length && (sortedChildren.length > 1 || !!group.exact);

        if(!shouldGroup)
        {
            if(group.exact) groupedEntries.push(group.exact);
            groupedEntries.push(...sortedChildren.map(child => ({ ...child, label: child.displayLabel })));
            continue;
        }

        groupedEntries.push({
            ...(group.exact || {
                id: `${ GROUP_TOKEN_PREFIX }${ rootKey }`,
                token: `${ GROUP_TOKEN_PREFIX }${ rootKey }`,
                label: rootKey,
                displayLabel: rootKey,
                searchableText: rootKey,
                selectable: false,
                hasValue: false,
                kind: (sortedChildren[0]?.kind || 'custom'),
                target: (sortedChildren[0]?.target || 'user')
            }),
            label: rootKey,
            displayLabel: rootKey,
            searchableText: `${ rootKey } ${ sortedChildren.map(child => child.displayLabel).join(' ') }`,
            children: sortedChildren
        });
    }

    return groupedEntries
        .filter(entry => entry.displayLabel?.trim().length)
        .sort(sortEntries)
        .filter((entry, index, collection) => collection.findIndex(currentEntry => (currentEntry.token === entry.token)) === index);
};

export const createCustomVariableToken = (itemId: number) => (itemId > 0 ? `${ CUSTOM_TOKEN_PREFIX }${ itemId }` : '');
export const createInternalVariableToken = (key: string) =>
{
    const normalizedKey = normalizeInternalVariableKey(key);

    return normalizedKey ? `${ INTERNAL_TOKEN_PREFIX }${ normalizedKey }` : '';
};
export const isCustomVariableToken = (token: string) => !!token && token.startsWith(CUSTOM_TOKEN_PREFIX);
export const isInternalVariableToken = (token: string) => !!token && token.startsWith(INTERNAL_TOKEN_PREFIX);
export const getCustomVariableItemId = (token: string) => (isCustomVariableToken(token) ? parseInt(token.slice(CUSTOM_TOKEN_PREFIX.length), 10) || 0 : 0);
export const getInternalVariableKey = (token: string) => (isInternalVariableToken(token) ? normalizeInternalVariableKey(token.slice(INTERNAL_TOKEN_PREFIX.length)) : '');

export const normalizeVariableTokenFromWire = (value: string) =>
{
    const normalizedValue = value?.trim();

    if(!normalizedValue) return '';
    if(isCustomVariableToken(normalizedValue)) return normalizedValue;
    if(isInternalVariableToken(normalizedValue)) return createInternalVariableToken(normalizedValue.slice(INTERNAL_TOKEN_PREFIX.length));

    const parsedValue = parseInt(normalizedValue, 10);

    return (!Number.isNaN(parsedValue) && (parsedValue > 0)) ? createCustomVariableToken(parsedValue) : '';
};

export const createFallbackVariableEntry = (target: WiredVariablePickerTarget, token: string): IWiredVariablePickerEntry | null =>
{
    if(!token) return null;

    if(isCustomVariableToken(token))
    {
        const itemId = getCustomVariableItemId(token);

        if(itemId <= 0) return null;

        return {
            id: token,
            token,
            label: `#${ itemId }`,
            displayLabel: `#${ itemId }`,
            searchableText: `#${ itemId }`,
            selectable: true,
            hasValue: false,
            kind: 'custom',
            target
        };
    }

    if(isInternalVariableToken(token))
    {
        const key = getInternalVariableKey(token);

        if(!key) return null;

        return {
            id: token,
            token,
            label: key,
            displayLabel: key,
            searchableText: key,
            selectable: false,
            hasValue: false,
            kind: 'internal',
            target
        };
    }

    return null;
};

export const buildWiredVariablePickerEntries = (target: WiredVariablePickerTarget, usage: WiredVariablePickerUsage, customDefinitions: IWiredVariableDefinitionLike[]) =>
{
    const internalTarget = getNormalizedInternalTarget(target);
    const customEntries = groupEntries([ ...(customDefinitions || []) ]
        .map(definition => createCustomEntry(target, usage, definition))
        .sort(sortEntries));
    const internalEntries = groupEntries(INTERNAL_VARIABLES[internalTarget].map(meta => createInternalEntry(target, usage, meta)));

    return [ ...customEntries, ...internalEntries ];
};

export const flattenWiredVariablePickerEntries = (entries: IWiredVariablePickerEntry[]): IWiredVariablePickerEntry[] =>
{
    const flattened: IWiredVariablePickerEntry[] = [];

    for(const entry of entries)
    {
        flattened.push(entry);

        if(entry.children?.length) flattened.push(...entry.children);
    }

    return flattened;
};
