import * as asyncApi from '@asyncapi/parser';
import { AvroSchemaParser } from '@asyncapi/avro-schema-parser';
import path from 'node:path';

import type { AsyncAPIDocumentInterface } from '@asyncapi/parser';
import type { PageStaticData } from '@redocly/realm/dist/shared/types';
import type { Feature } from '@redocly/realm/dist/server/entitlements/entitlements.types';
import type {
  GetStaticDataFn,
  PageRouteDetails,
  ExternalPlugin,
} from '@redocly/realm/dist/server/plugins/types';
import type { AsyncApiDocsSettings } from './config';

import { asyncApiDocsConfigSchema } from './config.js';

const ASYNCAPI_TEMPLATE_ID = 'asyncapi-docs';
const ASYNCAPI_SHARED_DATA_PREFIX = 'asyncapi-docs-';

export default function asyncAPIDocsPlugin(): ExternalPlugin {
  const parser = new asyncApi.Parser();
  parser.registerSchemaParser(AvroSchemaParser());

  return {
    id: 'asyncapi',
    requiredEntitlements: ['asyncapi' as Feature],
    redoclyConfigSchema: asyncApiDocsConfigSchema as any,
    loaders: {
      asyncapi: async (relativePath, { fs, cache, logger }) => {
        const absolutePath = path.join(fs.cwd, relativePath);
        const { data: yaml } = await cache.load<any>(relativePath, 'yaml');
        if (!yaml.asyncapi) return undefined;

        const { document, diagnostics } = await asyncApi.fromFile(parser, absolutePath).parse();

        diagnostics
          .filter((d) => d.severity === 0)
          .forEach(({ message, path }) =>
            logger.error(
              `Cannot parse AsyncAPI schema: ${message} in '${relativePath}:${path.join('.')}'`,
            ),
          );

        return document;
      },
    },
    processContent: async (actions, context) => {
      const asyncAPITemplateId = actions.createTemplate(
        ASYNCAPI_TEMPLATE_ID,
        '@redocly/portal-plugin-async-api/template.js',
      );

      const config = await context.getConfig();
      const globalSettings =
        (config as { asyncapi: AsyncApiDocsSettings } | undefined)?.asyncapi || {};

      for (const record of await context.fs.scan(/(\.ya?ml|\.json)$/)) {
        if (await context.isPathIgnored(record.relativePath)) continue;
        const { relativePath } = record;

        const sharedDataId = `${ASYNCAPI_SHARED_DATA_PREFIX}${relativePath}`;
        try {
          const { data: document } = await context.cache.load<AsyncAPIDocumentInterface>(
            record.realRelativePath,
            'asyncapi',
          );

          if (!document) continue;

          await actions.createSharedData(sharedDataId, document?.json() ?? {});

          actions.addRoute({
            fsPath: relativePath,
            templateId: asyncAPITemplateId,
            getStaticData: buildGetStaticDataFn(globalSettings, context.withPathPrefix),
            metadata: {
              type: 'asyncapi',
              title: document?.json()?.info?.title ?? 'AsyncAPI',
              description: document?.json()?.info?.description ?? '',
              ...(document?.json()?.info?.['x-metadata'] ?? {}),
            },
            sharedData: [{ key: 'AsyncApiDefinition', id: sharedDataId }],
          });
        } catch (e) {
          console.error(e);
        }
      }
    },
  };

  function buildGetStaticDataFn(
    settings: AsyncApiDocsSettings,
    withPathPrefix: (url: string) => string,
  ): GetStaticDataFn<PageRouteDetails, PageStaticData> {
    return async function (route, _context) {
      return {
        props: {
          settings: {
            ...settings,
            baseUrlPath: withPathPrefix(route.slug),
          },
        },
      };
    };
  }
}
