// Copyright 2017-2025 @pezkuwi/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { TypeRegistry } from '@pezkuwi/types/create';
import type { Definitions } from '@pezkuwi/types/types';
import type { ExtraTypes } from './types.js';

import Handlebars from 'handlebars';

import * as defaultDefinitions from '@pezkuwi/types/interfaces/definitions';
import staticBizinikiwi from '@pezkuwi/types-support/metadata/static-bizinikiwi';

import { createImports, formatType, getSimilarTypes, initMeta, readTemplate, setImports, writeFile } from '../util/index.js';

interface ItemDef {
  args: string;
  docs: string[];
  generic: string | undefined;
  name: string;
  type: string | undefined;
}

interface ModuleDef {
  items: ItemDef[];
  name: string;
}

const StorageKeyType = 'StorageKey | string | Uint8Array | any';

const generateRpcTypesTemplate = Handlebars.compile(readTemplate('rpc'));

/** @internal */
export function generateRpcTypes (registry: TypeRegistry, importDefinitions: Record<string, Definitions>, dest: string, extraTypes: ExtraTypes): void {
  writeFile(dest, (): string => {
    const allTypes: ExtraTypes = { '@pezkuwi/types/interfaces': importDefinitions, ...extraTypes };
    const imports = createImports(allTypes);
    const definitions = imports.definitions as Record<string, Definitions>;
    const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
      return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
    }, {});

    const rpcKeys = Object
      .keys(definitions)
      .filter((key) => Object.keys(definitions[key].rpc || {}).length !== 0)
      .sort();

    const additional: Record<string, ModuleDef> = {};
    const modules = rpcKeys.map((sectionFullName) => {
      const rpc = definitions[sectionFullName].rpc || {};
      const section = sectionFullName.split('/').pop();

      const allMethods = Object.keys(rpc).sort().map((methodName) => {
        const def = rpc[methodName];

        let args;
        let type;
        let generic;

        // These are too hard to type with generics, do manual overrides
        if (section === 'state') {
          setImports(allDefs, imports, ['Codec', 'Hash', 'StorageKey', 'Vec']);

          if (methodName === 'getStorage') {
            generic = 'T = Codec';
            args = [`key: ${StorageKeyType}, block?: Hash | Uint8Array | string`];
            type = 'T';
          } else if (methodName === 'queryStorage') {
            generic = 'T = Codec[]';
            args = [`keys: Vec<StorageKey> | (${StorageKeyType})[], fromBlock?: Hash | Uint8Array | string, toBlock?: Hash | Uint8Array | string`];
            type = '[Hash, T][]';
          } else if (methodName === 'queryStorageAt') {
            generic = 'T = Codec[]';
            args = [`keys: Vec<StorageKey> | (${StorageKeyType})[], at?: Hash | Uint8Array | string`];
            type = 'T';
          } else if (methodName === 'subscribeStorage') {
            generic = 'T = Codec[]';
            args = [`keys?: Vec<StorageKey> | (${StorageKeyType})[]`];
            type = 'T';
          }
        }

        if (args === undefined) {
          setImports(allDefs, imports, [def.type]);

          args = def.params.map((param) => {
            const similarTypes = getSimilarTypes(registry, definitions, param.type, imports);

            setImports(allDefs, imports, [param.type, ...similarTypes]);

            return `${param.name}${param.isOptional ? '?' : ''}: ${similarTypes.join(' | ')}`;
          });

          type = formatType(registry, allDefs, def.type, imports);
          generic = '';
        }

        const item = {
          args: args.join(', '),
          docs: def.deprecated
            ? [`@deprecated ${def.deprecated}`, def.description]
            : [def.description],
          generic,
          name: methodName,
          type
        };

        if (def.aliasSection) {
          if (!additional[def.aliasSection]) {
            additional[def.aliasSection] = {
              items: [],
              name: def.aliasSection
            };
          }

          additional[def.aliasSection].items.push(item);

          return null;
        }

        return item;
      }).filter((item): item is ItemDef => !!item);

      return {
        items: allMethods,
        name: section || 'unknown'
      };
    }).concat(...Object.values(additional)).sort((a, b) => a.name.localeCompare(b.name));

    imports.typesTypes['Observable'] = true;

    return generateRpcTypesTemplate({
      headerType: 'chain',
      imports,
      modules,
      types: [
        ...Object.keys(imports.localTypes).sort().map((packagePath): { file: string; types: string[] } => ({
          file: packagePath.replace('@pezkuwi/types-augment', '@pezkuwi/types'),
          types: Object.keys(imports.localTypes[packagePath])
        })),
        {
          file: '@pezkuwi/rpc-core/types',
          types: ['AugmentedRpc']
        }
      ]
    });
  });
}

export function generateDefaultRpc (dest = 'packages/rpc-augment/src/augment/jsonrpc.ts', extraTypes: ExtraTypes = {}): void {
  const { registry } = initMeta(staticBizinikiwi, extraTypes);

  generateRpcTypes(
    registry,
    defaultDefinitions,
    dest,
    extraTypes
  );
}
