import type { Document, TagObject } from '@/types';
import { stringify } from 'yaml';
import {
  type GeneratedPageProps,
  getPageProps,
  type OperationItem,
  type OperationOutput,
  type PageOutput,
  type WebhookItem,
  type WebhookOutput,
} from '@/utils/pages/builder';
import type { InternalOpenAPIMeta } from '@/server';
import { toStaticData } from '@/utils/pages/to-static-data';
import { doubleQuote } from '@/requests/string-utils';

export interface PagesToTextOptions {
  /**
   * Additional imports of your MDX components.
   */
  imports?: {
    names: string[];
    from: string;
  }[];

  /**
   * Customize frontmatter.
   *
   * A `full: true` property will be added by default.
   */
  frontmatter?: (
    title: string,
    description: string | undefined,
    context: DocumentContext,
  ) => Record<string, unknown>;

  /**
   * Add description to document body.
   *
   * We recommend to enable it, it is disabled by default because some OpenAPI schemas have invalid description that breaks Markdown syntax.
   *
   * @defaultValue false
   */
  includeDescription?: boolean;

  /**
   * Add a comment to the top of generated files indicating they are auto-generated.
   * - `true`: Adds a standardized comment
   * - `false`: No comment is added
   * - `string`: Adds the provided custom comment
   *
   * @defaultValue true
   */
  addGeneratedComment?: boolean | string;
}

export function toText(
  entry: PageOutput | OperationOutput | WebhookOutput,
  doc: Document,
  options: PagesToTextOptions = {},
) {
  const { frontmatter, includeDescription = false } = options;
  const extend = frontmatter?.(
    entry.info.title,
    entry.info.description,
    entry.type === 'page'
      ? entry.tag
        ? { type: 'tag', tag: entry.tag }
        : { type: 'file' }
      : { type: 'operation' },
  );
  const pageProps = getPageProps(entry);
  if (!includeDescription) {
    pageProps.showDescription = false;
  }

  let meta: InternalOpenAPIMeta = {
    preload: [entry.schemaId],
  };
  if (entry.type === 'operation' || entry.type === 'webhook') {
    const operation = entry.item;

    meta.method = operation.method.toUpperCase();
    meta.webhook = entry.type === 'webhook';
    meta.deprecated = entry.info.deprecated;
  }

  const data = toStaticData(pageProps, doc);

  return generateDocument(
    {
      title: entry.info.title,
      description: !includeDescription ? entry.info.description : undefined,
      full: true,
      ...extend,
      _openapi: {
        ...meta,
        ...data,
        ...(extend?._openapi as object | undefined),
      },
    },
    pageContent(pageProps),
    options,
  );
}

export function generateDocument(
  frontmatter: unknown,
  content: string,
  options: PagesToTextOptions,
): string {
  const { addGeneratedComment = true, imports } = options;
  const out: string[] = [];
  const banner = stringify(frontmatter, {
    compat: 'yaml-1.1',
    singleQuote: true,
  }).trimEnd();
  if (banner.length > 0) out.push(`---\n${banner}\n---`);

  if (addGeneratedComment) {
    let commentContent =
      'This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again.';

    if (typeof addGeneratedComment === 'string') {
      commentContent = addGeneratedComment;
    }

    commentContent = commentContent.replaceAll('/', '\\/');
    out.push(`{/* ${commentContent} */}`);
  }

  if (imports) {
    out.push(
      ...imports
        .map((item) => `import { ${item.names.join(', ')} } from ${doubleQuote(item.from)};`)
        .join('\n'),
    );
  }

  out.push(content);
  return out.join('\n\n');
}

export type DocumentContext =
  | {
      type: 'tag';
      tag: TagObject;
    }
  | {
      type: 'operation';
    }
  | {
      type: 'file';
    };

function pageContent({
  showTitle,
  showDescription,
  document,
  webhooks,
  operations,
}: GeneratedPageProps): string {
  const propStrs: string[] = [`document=${doubleQuote(document)}`];

  // filter extra properties in props
  if (webhooks) {
    propStrs.push(
      `webhooks={${JSON.stringify(
        webhooks.map(
          (item) =>
            ({
              name: item.name,
              method: item.method,
            }) satisfies WebhookItem,
        ),
      )}}`,
    );
  }
  if (operations) {
    propStrs.push(
      `operations={${JSON.stringify(
        operations.map(
          (item) =>
            ({
              path: item.path,
              method: item.method,
            }) satisfies OperationItem,
        ),
      )}}`,
    );
  }
  if (showTitle) {
    propStrs.push(`showTitle`);
  }
  if (showDescription) {
    propStrs.push(`showDescription`);
  }

  return `export default function Layout(props) {
  const { APIPage, OpenAPIPage } = props.components ?? {};
  // "APIPage" is the old name from v10, this allows both for backward compatibility
  const Comp = OpenAPIPage ?? APIPage;
  return (
    <>
      {props.children}
      <Comp ${propStrs.join(' ')} />
    </>
  );
}`;
}
