import GitHub from '@material-ui/icons/GitHub';
import { MDXProvider } from '@mdx-js/react';
import { inspect } from '@xstate/inspect';
import { useInterpret } from '@xstate/react';
import { GetStaticPaths, InferGetStaticPropsType, NextPage } from 'next';
import Head from 'next/head';
import Link from 'next/link';
import React, { useEffect, useRef, useState } from 'react';
import { StateMachine } from 'xstate';
import { useLayout } from '../../lib/GlobalState';
import {
  Action,
  Context,
  Event,
  Guard,
  MachineHelpersContext,
  MDXMetadata,
  Service,
  State,
  WholeContext,
} from '../../lib/MachineHelpers';
import { metadata, MetadataItem } from '../../lib/metadata';
import { useCopyToClipboard } from '../../lib/useCopyToClipboard';

const useGetImports = (slug: string, deps: any[]) => {
  const [imports, setImports] = useState<{
    machine: StateMachine<any, any, any>;
    mdxDoc: any;
    mdxMetadata?: MDXMetadata;
  }>();

  const getMachine = async () => {
    setImports(undefined);
    const machineImport: {
      default: StateMachine<any, any, any>;
    } = await import(`../../lib/machines/${slug}.machine.ts`);

    const mdxDoc = await import(`../../lib/machines/${slug}.mdx`);

    setImports({
      machine: machineImport.default,
      mdxDoc: mdxDoc.default,
      mdxMetadata: mdxDoc.metadata,
    });
  };

  useEffect(() => {
    getMachine();
  }, [slug, ...deps]);

  return imports;
};

export const getStaticProps = async ({ params }) => {
  const fs = await import('fs');
  const path = await import('path');

  const machinesPath = path.resolve(
    process.cwd(),
    'lib/machines',
    `${params.id}.machine.ts`,
  );

  const meta = metadata[params.id];

  if (!meta) {
    throw new Error(
      `Could not find metadata for ${params.id}. Go to lib/metadata.ts to fix.`,
    );
  }

  return {
    props: {
      slug: params.id as string,
      fileText: fs.readFileSync(machinesPath).toString(),
      meta,
    },
  };
};

const MachinePage: NextPage<InferGetStaticPropsType<typeof getStaticProps>> = (
  props,
) => {
  const layout = useLayout();
  const imports = useGetImports(props.slug, [layout]);

  const iframeRef = useRef(null);
  useEffect(() => {
    const { disconnect } = inspect({
      iframe: () => iframeRef.current,
    });

    return () => {
      disconnect();
    };
  }, [layout, props.slug]);

  return (
    <>
      <Head>
        <title>{props.meta.title} | XState Catalogue</title>
      </Head>
      <Layout
        content={
          <>
            {imports && (
              <ShowMachinePage
                slug={props.slug}
                machine={imports.machine}
                mdxDoc={imports.mdxDoc}
                fileText={props.fileText}
                meta={props.meta}
                mdxMetadata={imports.mdxMetadata}
              ></ShowMachinePage>
            )}
          </>
        }
        iframe={
          <iframe key="iframe" ref={iframeRef} className="w-full h-full" />
        }
      ></Layout>
    </>
  );
};

const Layout = (props: {
  content: React.ReactNode;
  iframe: React.ReactNode;
}) => {
  const layout = useLayout();
  if (layout === 'horizontal' || layout === 'vertical') {
    return (
      <div
        className={`md:grid h-full ${
          layout === 'horizontal' ? 'md:grid-cols-2' : 'md:grid-rows-2'
        }`}
      >
        <div className="hidden bg-black md:block">{props.iframe}</div>
        <div className="overflow-y-scroll md:pt-12">
          <div>{props.content}</div>
        </div>
      </div>
    );
  }
  if (layout === 'blog') {
    return (
      <div className="h-full overflow-y-scroll">
        <div>
          <div
            style={{ height: '550px' }}
            className="hidden mb-16 bg-black md:block"
          >
            {props.iframe}
          </div>
          <div>{props.content}</div>
        </div>
      </div>
    );
  }

  return null;
};

const ShowMachinePage = (props: {
  machine: StateMachine<any, any, any>;
  mdxDoc: any;
  fileText: string;
  slug: string;
  meta: MetadataItem;
  mdxMetadata?: MDXMetadata;
}) => {
  const service = useInterpret(props.machine, {
    devTools: true,
  });
  const [hasDismissed, setHasDismissed] = useState<boolean>(
    Boolean(localStorage.getItem('REJECTED_1')),
  );

  const copyToClipboard = useCopyToClipboard({});

  const fileTextRef = useRef(null);

  useEffect(() => {
    // @ts-ignore
    const hljs: any = window.hljs;
    if (hljs) {
      hljs.highlightBlock(fileTextRef.current);
    }
  }, [fileTextRef, props.fileText]);

  return (
    <MachineHelpersContext.Provider
      value={{ service, metadata: props.mdxMetadata }}
    >
      <div className="flex justify-center">
        <div className="">
          {!hasDismissed && (
            <div className="flex justify-center mb-16">
              <div className="relative max-w-xl p-6 space-y-4 text-gray-600 bg-gray-50">
                <div className="flex items-center space-x-3">
                  <span className="text-3xl">💡</span>
                  <span className="text-xl font-semibold tracking-tighter">
                    By the way!
                  </span>
                </div>
                <p className="text-gray-500 leading-">
                  You can interact with the state machine in the article below
                  by pressing on the <Event>EVENT</Event> buttons. They'll show
                  up as yellow when they can be interacted with.
                </p>
                <button
                  className="absolute top-0 right-0 p-2 mb-2 mr-4 text-lg"
                  onClick={() => {
                    setHasDismissed(true);
                    localStorage.setItem('REJECTED_1', 'true');
                  }}
                >
                  <span className="text-gray-600">✖</span>
                </button>
              </div>
            </div>
          )}
          <div className="flex">
            <SideBar machine={props.machine} />
            <div className="p-6 space-y-6">
              <div className="space-x-4 text-xs font-medium tracking-tight text-gray-500">
                <a
                  href={`https://github.com/mattpocock/xstate-catalogue/edit/master/lib/machines/${props.slug}.machine.ts`}
                  className="inline-flex items-center px-2 py-1 pr-1 space-x-2 text-gray-500 border border-gray-200 rounded"
                  target="_blank"
                >
                  <span>Edit</span>
                  <GitHub style={{ height: '1rem', width: '1.2rem' }} />
                </a>
                <a
                  href={`https://github.com/mattpocock/xstate-catalogue/discussions?discussions_q=${props.meta.title}`}
                  className="inline-flex items-center px-2 py-1 pr-1 space-x-2 text-gray-500 border border-gray-200 rounded"
                  target="_blank"
                >
                  <span>Discuss</span>
                  <GitHub style={{ height: '1rem', width: '1.2rem' }} />
                </a>
              </div>
              <div className="prose lg:prose-lg">
                <MDXProvider
                  components={{
                    Event,
                    State,
                    Action,
                    Service,
                    Context,
                    WholeContext,
                  }}
                >
                  <props.mdxDoc></props.mdxDoc>
                </MDXProvider>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div className="mt-16">
        <div className="p-6 xl:p-12 -mb-20 text-gray-100 bg-gray-900">
          <div className="container relative max-w-6xl mx-auto">
            <pre>
              <code ref={fileTextRef} className="lang-ts">
                {props.fileText}
              </code>
            </pre>
            <button
              className="invisible md:visible absolute top-0 right-0 px-6 py-3 mr-8 font-bold tracking-tight text-gray-100 bg-blue-700 rounded-lg"
              onClick={() => {
                copyToClipboard(props.fileText);
              }}
            >
              Copy To Clipboard
            </button>
          </div>
        </div>
      </div>
    </MachineHelpersContext.Provider>
  );
};

const machinePathRegex = /\.machine\.ts$/;

export const getStaticPaths: GetStaticPaths = async () => {
  const fs = await import('fs');
  const path = await import('path');

  const machinesPath = path.resolve(process.cwd(), 'lib/machines');

  const machines = fs.readdirSync(machinesPath);

  return {
    fallback: false,
    paths: machines
      .filter((machine) => machine.endsWith('.ts'))
      .map((fileName) => {
        return {
          params: {
            id: fileName.replace(machinePathRegex, ''),
          },
        };
      }),
  };
};

export default MachinePage;

const SideBar = (props: { machine: StateMachine<any, any, any> }) => {
  return (
    <div
      className="hidden p-6 space-y-16 border-r md:block"
      style={{ maxWidth: '300px' }}
    >
      <div className="w-48" />
      <Link href="/#Catalogue">
        <a className="space-x-3 text-base text-gray-600">
          <span className="text-gray-500">{'❮'}</span>
          <span>Back to List</span>
        </a>
      </Link>
      <div className="space-y-3">
        <h2 className="text-base font-semibold tracking-tighter text-gray-500">
          States
        </h2>
        <ul className="space-y-3">
          {props.machine.stateIds.map((id) => {
            if (id === props.machine.id) return null;
            return (
              <li key={`MACHINE ID: ${id}`}>
                <State>
                  {props.machine.getStateNodeById(id).path.join('.')}
                </State>
              </li>
            );
          })}
        </ul>
      </div>
      <div className="space-y-3">
        <h2 className="text-base font-semibold tracking-tighter text-gray-500">
          Events
        </h2>
        <ul className="space-y-3">
          {props.machine.events
            .filter((event) => !event.startsWith('xstate.') && event)
            .map((event) => {
              return (
                <li key={`EVENT TYPE: ${event}`}>
                  <Event>{event}</Event>
                </li>
              );
            })}
        </ul>
      </div>
      {Object.keys(props.machine.options.actions).length > 0 && (
        <div className="space-y-3">
          <h2 className="text-base font-semibold tracking-tighter text-gray-500">
            Actions
          </h2>
          <ul className="space-y-3">
            {Object.keys(props.machine.options.actions).map((action) => {
              return (
                <li key={`ACTION: ${action}`}>
                  <Action>{action}</Action>
                </li>
              );
            })}
          </ul>
        </div>
      )}
      {Object.keys(props.machine.options.guards).length > 0 && (
        <div className="space-y-3">
          <h2 className="text-base font-semibold tracking-tighter text-gray-500">
            Guards
          </h2>
          <ul className="space-y-3">
            {Object.keys(props.machine.options.guards).map((guard) => {
              return (
                <li key={`GUARD: ${guard}`}>
                  <Guard>{guard}</Guard>
                </li>
              );
            })}
          </ul>
        </div>
      )}
      {Object.keys(props.machine.options.services).length > 0 && (
        <div className="space-y-3">
          <h2 className="text-base font-semibold tracking-tighter text-gray-500">
            Services
          </h2>
          <ul className="space-y-3">
            {Object.keys(props.machine.options.services).map((service) => {
              return (
                <li key={`SERVICE: ${service}`}>
                  <Service>{service}</Service>
                </li>
              );
            })}
          </ul>
        </div>
      )}
    </div>
  );
};