import type {
  IconifyIcon as IconifyIconData,
  IconifyJSON,
} from '@iconify/vue/offline';

import {
  addCollection as addOfflineCollection,
  addIcon as addOfflineIcon,
  Icon as IconifyIcon,
} from '@iconify/vue/offline';

import { LOCAL_ICON_DATA } from './local-icons';
import { STARTUP_ICON_DATA } from './startup-icons';

type LazyCollectionPrefix = 'ant-design' | 'ep' | 'mdi';

const LOCAL_ICON_NAMES = new Set<string>();
const COLLECTION_LOADERS: Record<
  LazyCollectionPrefix,
  () => Promise<IconifyJSON>
> = {
  'ant-design': async () => {
    const collection = await import('@iconify/json/json/ant-design.json');
    return collection.default;
  },
  ep: async () => {
    const collection = await import('@iconify/json/json/ep.json');
    return collection.default;
  },
  mdi: async () => {
    const collection = await import('@iconify/json/json/mdi.json');
    return collection.default;
  },
};
const COLLECTION_LOAD_PROMISES = new Map<LazyCollectionPrefix, Promise<void>>();

function getCollectionPrefix(data: IconifyJSON, prefix?: boolean | string) {
  if (prefix === false) {
    return '';
  }
  return `${typeof prefix === 'string' ? prefix : data.prefix}:`;
}

/**
 * Register an icon collection in the offline Iconify store and local name index.
 */
function registerCollection(data: IconifyJSON, prefix?: boolean | string) {
  addOfflineCollection(data, prefix);
  const iconPrefix = getCollectionPrefix(data, prefix);
  Object.keys(data.icons).forEach((name) => {
    LOCAL_ICON_NAMES.add(`${iconPrefix}${name}`);
  });
  Object.keys(data.aliases ?? {}).forEach((name) => {
    LOCAL_ICON_NAMES.add(`${iconPrefix}${name}`);
  });
}

/**
 * Register a single icon in the offline Iconify store.
 */
function registerIcon(name: string, data: IconifyIconData) {
  addOfflineIcon(name, data);
  LOCAL_ICON_NAMES.add(name);
}

/**
 * Return locally registered icon names for the requested collection.
 */
function listIcons(provider = '', prefix = ''): string[] {
  const providerPrefix = provider ? `@${provider}:` : '';
  const iconPrefix = prefix ? `${prefix}:` : '';
  return [...LOCAL_ICON_NAMES].filter((name) =>
    name.startsWith(`${providerPrefix}${iconPrefix}`),
  );
}

/**
 * Check whether an icon name is available without a network request.
 */
function isLocalIcon(name: string) {
  return LOCAL_ICON_NAMES.has(name);
}

function isLazyCollectionPrefix(
  prefix: string,
): prefix is LazyCollectionPrefix {
  return Object.prototype.hasOwnProperty.call(COLLECTION_LOADERS, prefix);
}

/**
 * Load a bundled offline icon collection once, without using a remote API.
 */
async function loadIconCollection(prefix: string) {
  if (!isLazyCollectionPrefix(prefix)) {
    return listIcons('', prefix);
  }

  let pending = COLLECTION_LOAD_PROMISES.get(prefix);
  if (!pending) {
    pending = COLLECTION_LOADERS[prefix]()
      .then((data) => {
        registerCollection(data);
      })
      .catch((error) => {
        COLLECTION_LOAD_PROMISES.delete(prefix);
        throw error;
      });
    COLLECTION_LOAD_PROMISES.set(prefix, pending);
  }
  await pending;
  return listIcons('', prefix);
}

/**
 * Preserve historical menu icons by loading only the collections they need.
 */
async function loadMissingIconCollections(iconNames: string[]) {
  const prefixes = new Set<LazyCollectionPrefix>();
  iconNames.forEach((name) => {
    if (isLocalIcon(name)) {
      return;
    }
    const prefix = name.split(':', 1)[0] ?? '';
    if (isLazyCollectionPrefix(prefix)) {
      prefixes.add(prefix);
    }
  });
  await Promise.all(
    [...prefixes].map(async (prefix) => {
      try {
        await loadIconCollection(prefix);
      } catch (error) {
        console.error(`Failed to load menu icon collection: ${prefix}`, error);
      }
    }),
  );
}

Object.entries({ ...LOCAL_ICON_DATA, ...STARTUP_ICON_DATA }).forEach(
  ([name, data]) => {
    registerIcon(name, data);
  },
);

export * from './create-icon';

export * from './lucide';

export type { IconifyIcon as IconifyIconStructure } from '@iconify/vue/offline';
export {
  registerCollection as addCollection,
  registerIcon as addIcon,
  IconifyIcon,
  isLocalIcon,
  listIcons,
  loadIconCollection,
  loadMissingIconCollections,
};
