import { XCircleIcon } from '@heroicons/react/solid';
import { getSchemaFromReference } from '@nrwl/nx-dev/data-access-packages';
import { JsonSchema1, NxSchema } from '@nrwl/nx-dev/models-package';
import { renderMarkdown } from '@nrwl/nx-dev/ui-markdoc';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { ReactNode, useState } from 'react';
import { generateJsonExampleFor, isErrors } from './examples';
import { SchemaViewModel } from './get-schema-view-model';
import { SchemaEditor } from './schema-editor';
import { SchemaViewer } from './schema-viewer';
import { Heading2, Heading3 } from './ui/headings';

function pathCleaner(path: string): string {
  return path.split('?')[0];
}

export function Content({
  schemaViewModel,
}: {
  schemaViewModel: SchemaViewModel;
}) {
  if (!schemaViewModel.currentSchema)
    throw new Error(
      'A valid schema has to be defined for the "currentSchema" property'
    );

  const router = useRouter();
  const [presets, setPresets] = useState<string[]>([]);
  const filterWithPresets = (
    data: Record<string, any>,
    wantedProperties: string[]
  ): Record<string, any> => {
    const result: Record<string, any> = {};

    if (!wantedProperties.length) return data;

    for (const p in data) {
      if (wantedProperties.includes(p)) {
        result[p] = data[p];
      }
    }

    return result;
  };

  const vm = {
    get fullExample(): Record<string, any> {
      const examples = generateJsonExampleFor(
        schemaViewModel.currentSchema as NxSchema,
        schemaViewModel.lookup,
        'both'
      );
      return isErrors(examples) ? {} : examples.value;
    },
    get pages(): { name: string; href: string; current: boolean }[] {
      return [
        {
          name: schemaViewModel.packageName,
          href: schemaViewModel.packageUrl,
          current: false,
        },
        {
          name: schemaViewModel.schemaMetadata.name,
          href: pathCleaner(router.asPath),
          current: !schemaViewModel.subReference,
        },
        !!schemaViewModel.subReference
          ? {
              name: schemaViewModel.subReference.split('/')[2],
              href: pathCleaner(router.asPath),
              current: true,
            }
          : void 0,
      ].filter(
        (x): x is { name: string; href: string; current: boolean } => !!x
      );
    },
    get markdown(): ReactNode {
      return renderMarkdown({
        content: getMarkdown({
          type: schemaViewModel.type,
          packageName: schemaViewModel.packageName,
          schemaName: schemaViewModel.schemaMetadata.name,
          schemaAlias: schemaViewModel.schemaMetadata.aliases[0] ?? '',
          schema: schemaViewModel.currentSchema as NxSchema,
        }),
        filePath: '',
        data: {},
      });
    },
  };

  return (
    <>
      <div className="min-w-0 flex-auto pt-8 pb-24 lg:pb-16">
        <div className="mb-8 flex w-full items-center space-x-2">
          <div className="w-full flex-grow">
            <div
              aria-hidden="true"
              data-tooltip="Schema type"
              className="relative inline-flex rounded-md border border-gray-200 bg-white px-4 py-2 text-xs font-medium uppercase text-gray-600"
            >
              {schemaViewModel.type}
            </div>
          </div>
          <div className="relative z-0 inline-flex flex-shrink-0 rounded-md shadow-sm">
            <Link href={schemaViewModel.packageUrl}>
              <a
                title="See package information"
                className="focus:ring-blue-nx-base focus:border-blue-nx-base relative inline-flex items-center rounded-l-md border border-gray-200 bg-white px-4 py-2 text-xs font-medium text-gray-600 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1"
              >
                {schemaViewModel.packageName}
              </a>
            </Link>
            <a
              href={schemaViewModel.schemaGithubUrl}
              target="_blank"
              rel="noreferrer"
              title="See this schema on Github"
              className="focus:ring-blue-nx-base focus:border-blue-nx-base relative -ml-px inline-flex items-center rounded-r-md border border-gray-200 bg-white px-4 py-2 text-xs font-medium text-gray-600 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1"
            >
              See schema on Github
            </a>
          </div>
        </div>

        {/* We remove the top description on sub property lookup */}
        {!schemaViewModel.subReference && (
          <>
            <div className="prose max-w-none">{vm.markdown}</div>
            <div className="h-12">{/* SPACER */}</div>
          </>
        )}

        {/*TODO@ben: create new component*/}
        {schemaViewModel.type === 'executors' && !schemaViewModel.subReference && (
          <div className="mt-8 hidden md:block">
            <Heading2 title="Options playground" />
            <p className="my-6">
              Try out this interactive editor of the configuration object.
              Values are validated as you type and hovering over labels will
              give you more information.
            </p>
            {!!schemaViewModel.currentSchema.presets.length && (
              <>
                <Heading3 title="Examples" />
                <p className="my-4">
                  These buttons show the config object for specific common
                  tasks.
                </p>
                <div className="mb-4 flex flex-wrap gap-4">
                  {schemaViewModel.currentSchema.presets.map((p) => (
                    <button
                      key={'preset-' + p.name.toLowerCase()}
                      onClick={() => {
                        setPresets(p.keys);
                      }}
                      type="button"
                      className="focus:border-blue-nx-base focus:ring-blue-nx-base relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1"
                    >
                      {p.name}
                    </button>
                  ))}
                  {!!presets.length && (
                    <button
                      onClick={() => setPresets([])}
                      type="button"
                      className="focus:border-blue-nx-base focus:ring-blue-nx-base relative inline-flex items-center rounded-md border border-gray-200 bg-gray-50 px-2 py-1 text-xs font-medium text-gray-500 hover:bg-gray-100 focus:z-10 focus:outline-none focus:ring-1"
                    >
                      Reset <XCircleIcon className="ml-1.5 h-4 w-4" />
                    </button>
                  )}
                </div>
              </>
            )}
            <div className="rounded-md border border-gray-300 p-1">
              <SchemaEditor
                packageName={schemaViewModel.packageName}
                schemaName={schemaViewModel.schemaMetadata.name}
                type={schemaViewModel.type.replace('s', '') as any}
                content={filterWithPresets(vm.fullExample, presets)}
                schema={schemaViewModel.currentSchema}
              />
            </div>
          </div>
        )}

        <div className="mt-8">
          <Heading2 title="Options" />
          {!schemaViewModel.subReference && (
            <SchemaViewer
              schema={schemaViewModel.currentSchema}
              lookup={schemaViewModel.lookup}
              reference="#"
              stage={'both'}
            />
          )}
          {schemaViewModel.subReference && (
            <SchemaViewer
              schema={
                getSchemaFromReference(
                  schemaViewModel.subReference,
                  schemaViewModel.lookup
                ) as JsonSchema1
              }
              lookup={schemaViewModel.lookup}
              reference={schemaViewModel.subReference}
              stage={'both'}
            />
          )}
        </div>
      </div>
    </>
  );
}

const getMarkdown = (data: {
  packageName: string;
  schemaAlias: string;
  schemaName: string;
  schema: NxSchema;
  type: 'executors' | 'generators';
}): string => {
  const hasExamplesFile = !!data.schema['examplesFile'];
  const executorNotice: string = `Options can be configured in \`project.json\` when defining the executor, or when invoking it. Read more about how to configure targets and executors here: [https://nx.dev/configuration/projectjson#targets](https://nx.dev/configuration/projectjson#targets).`;

  return [
    `# ${data.packageName}:${data.schemaName}`,
    `\n\n`,
    data.schema.description,
    '\n\n',
    data.type === 'executors' ? executorNotice : '',
    `\n\n`,
    hasExamplesFile ? data.schema['examplesFile'] : '',
    data.type === 'generators'
      ? hasExamplesFile
        ? data.schema['examplesFile']
        : getUsage(data.packageName, data.schemaName, data.schemaAlias)
      : '',
    !!data.schema['examples']
      ? `### Examples \n ${data.schema['examples']
          .map(
            (e: any) => `${e.description}: \n \`\`\`bash\n${e.command}\n\`\`\``
          )
          .join('\n')}`
      : '',
    `\n\n`,
  ].join('');
};

const getUsage = (
  packageName: string,
  schemaName: string,
  schemaAlias: string
): string => `
## Usage
\`\`\`bash
nx generate ${schemaName} ...
\`\`\`
${!!schemaAlias ? `\`\`\`bash\nnx g ${schemaAlias} ... #same\n\`\`\`\n` : ''}

By default, Nx will search for \`${schemaName}\` in the default collection provisioned in workspace.json.

You can specify the collection explicitly as follows:

\`\`\`bash
nx g ${packageName}:${schemaName} ...
\`\`\`

Show what will be generated without writing to disk:

\`\`\`bash
nx g ${schemaName} ... --dry-run
\`\`\`
`;

export default Content;
