import { validate, getOpenApiDocumentFromUrl } from '@mintlify/common';
import { getBrokenInternalLinks, renameFilesAndUpdateLinksInContent } from '@mintlify/link-rot';
import { dev } from '@mintlify/previewing';
import Chalk from 'chalk';
import fs from 'fs/promises';
import yaml from 'js-yaml';
import path from 'path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';

import {
  checkPort,
  checkForMintJson,
  checkNodeVersion,
  upgradeConfig,
  checkForDocsJson,
} from './helpers.js';

export const cli = () =>
  yargs(hideBin(process.argv))
    .middleware(checkNodeVersion)
    .command(
      'dev',
      'Runs Mintlify project locally.',
      (yargs) =>
        yargs
          .option('open', {
            type: 'boolean',
            default: true,
            description: 'Open Mintlify in the browser',
          })
          .usage('Usage: mintlify dev [options]')
          .example('mintlify dev', 'Run with default settings (opens in browser)')
          .example('mintlify dev --no-open', 'Run without opening in browser'),
      async (argv) => {
        const port = await checkPort(argv);
        if (port != undefined) {
          await dev({
            ...argv,
            port,
          });
        } else {
          console.error(`No available port found.`);
        }
      }
    )
    .command(
      'openapi-check <openapiFilenameOrUrl>',
      'Validate an OpenAPI spec',
      (yargs) =>
        yargs.positional('openapiFilenameOrUrl', {
          describe:
            'The filename of the OpenAPI spec (e.g. ./openapi.yaml) or the URL to the OpenAPI spec (e.g. https://petstore3.swagger.io/api/v3/openapi.json)',
          type: 'string',
          demandOption: true,
        }),
      async ({ openapiFilenameOrUrl }) => {
        try {
          if (openapiFilenameOrUrl.startsWith('https://')) {
            await getOpenApiDocumentFromUrl(openapiFilenameOrUrl);
            console.log('✅ Your OpenAPI definition is valid.');
            process.exit(0);
          }
          const pathname = path.resolve(process.cwd(), openapiFilenameOrUrl);
          const file = await fs.readFile(pathname, 'utf-8');
          const document = yaml.load(file) as Record<string, unknown> | undefined;

          if (!document) {
            throw new Error(
              'Failed to parse OpenAPI spec: could not parse file correctly, please check for any syntax errors.'
            );
          }
          await validate(document);
          console.log('✅ Your OpenAPI definition is valid.');
        } catch (err) {
          console.error(Chalk.red(err));
          process.exit(1);
        }
      }
    )
    .command(
      'broken-links',
      'Check for broken links in your Mintlify project.',
      () => undefined,
      async () => {
        const hasMintJson = await checkForMintJson();
        if (!hasMintJson) {
          await checkForDocsJson();
        }

        console.log(Chalk.bold('Checking for broken links...\n'));
        try {
          const brokenLinks = await getBrokenInternalLinks();
          if (brokenLinks.length === 0) {
            console.log(Chalk.green('No broken links found.'));
            return;
          }

          const brokenLinksByFile: Record<string, string[]> = {};
          brokenLinks.forEach((mdxPath) => {
            const filename = path.join(mdxPath.relativeDir, mdxPath.filename);
            const brokenLinksForFile = brokenLinksByFile[filename];
            if (brokenLinksForFile) {
              brokenLinksForFile.push(mdxPath.originalPath);
            } else {
              brokenLinksByFile[filename] = [mdxPath.originalPath];
            }
          });
          Object.entries(brokenLinksByFile).forEach(([fileName, brokenLinks]) => {
            console.group(`${Chalk.underline(fileName)}`);
            console.log(brokenLinks.join('\n'), '\n');
            console.groupEnd();
          });
          console.error(Chalk.yellow(`${brokenLinks.length} broken links found.`));

          process.exit(1);
        } catch (err) {
          console.error(Chalk.red(err));
          process.exit(1);
        }
      }
    )
    .command(
      'rename <from> <to>',
      'Rename file in a Mintlify project and update the internal link references.',
      (yargs) =>
        yargs
          .positional('from', {
            describe: 'The file to rename',
            type: 'string',
          })
          .positional('to', {
            describe: 'The new name for the file',
            type: 'string',
          })
          .demandOption(['from', 'to'])
          .epilog('Example: `mintlify rename introduction.mdx overview.mdx`'),
      async ({ from, to }) => {
        const hasMintJson = await checkForMintJson();
        if (!hasMintJson) {
          await checkForDocsJson();
        }
        await renameFilesAndUpdateLinksInContent(from, to);
      }
    )
    .command(
      'upgrade',
      'Upgrade the mint.json file to v2 (docs.json)',
      () => undefined,
      async () => {
        const hasMintJson = await checkForMintJson();
        if (!hasMintJson) {
          await checkForDocsJson();
        }
        await upgradeConfig();
      }
    )
    // Print the help menu when the user enters an invalid command.
    .strictCommands()
    .demandCommand(1, 'Unknown command. See above for the list of supported commands.')

    // Alias option flags --help = -h, --version = -v
    .alias('h', 'help')
    .alias('v', 'version')

    .parse();
