GitFOSS
// 1st-party
import { Organization } from "@prisma/client";
import { ReqHandler } from "@ethicdevs/react-monolith";
// app
import type { RepositoryWithParentAndForkedFromRepos } from "../../types";
import { AppRoute, AppRouteParams } from "../../routes.defs";
import { Const } from "../../const";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
import { makeUsersService } from "../../services/user";
// app islands
import {
  PullRequestFormState,
  RepositoryPullRequestCreateFormVariant,
  RepositoryPullRequestTarget,
} from "../../islands/RepositoryPullRequestCreateForm";
// app views
import RepositoryPullRequestCreateView, {
  RepositoryPullRequestCreateViewProps,
} from "../../views/repositoryPullRequests/RepositoryPullRequestCreateView";

const getRepositoryPullRequestCreateView: ReqHandler<
  AppRouteParams,
  AppRoute.REPOSITORY_PULL_REQUEST_CREATE
> = async (request, reply) => {
  const { orgSlug, repoSlug } = request.params;
  const { from_branch } = request.query;

  if (
    request.session.data.authenticated === false ||
    request.session.data.curr_user_uid == null
  ) {
    reply.redirect(302, request.namedViewsPathMap[AppRoute.AUTH_LOGIN]);
    return reply;
  }

  const orgService = makeOrganizationService({ request });
  const repoService = makeRepositoryService({ request });
  const usersService = makeUsersService({ request });

  const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
  const repo = await repoService.getRepository(orgSlug, repoSlug);

  if (parentOrg == null || repo == null) {
    return reply.status(404).callNotFound();
  }

  const branches = await repoService.getRepositoryBranches(repo);

  const currentUserForks = await repoService.getCurrentUserRepositoryForks(
    repo
  );

  const currentUserForksRepos = await currentUserForks.reduce(
    async (accP, r) => {
      let acc = await accP;
      acc = {
        ...acc,
        [`${r.organization.slug}/${r.slug}`]: {
          repo: r,
          branches: await repoService.getRepositoryBranches(r),
        },
      };
      return acc;
    },
    Promise.resolve({}) as Promise<{
      [repoSlug: string]: {
        repo: RepositoryWithParentAndForkedFromRepos;
        branches: string[];
      };
    }>
  );

  // Inject PR target's repository first.
  const [personalOrg] = await usersService.getUserOrganizations(
    request.session.data.curr_user_uid,
    true // personalOnly
  );

  const sources: {
    [orgSlug: string]: {
      org: Organization;
      repos: {
        [repoKey: string]: {
          repo: RepositoryWithParentAndForkedFromRepos;
          branches: string[];
        };
      };
    };
  } = {
    // target repo's source
    [parentOrg.slug]: {
      org: parentOrg,
      repos: {
        [`${parentOrg.slug}/${repo.slug}`]: {
          repo: { ...repo, organization: parentOrg },
          branches: branches.sort((a, b) => {
            if (
              a === Const.PRIMARY_BRANCH_REF &&
              b !== Const.PRIMARY_BRANCH_REF
            ) {
              return -1;
            }
            if (
              a !== Const.PRIMARY_BRANCH_REF &&
              b === Const.PRIMARY_BRANCH_REF
            ) {
              return 1;
            }
            return 0;
          }),
        },
      },
    },
    // In case user has forks on its own, add its personal org (where forks **should** be)
    ...(currentUserForks.length >= 1 && {
      [personalOrg.slug]: {
        org: personalOrg,
        repos: currentUserForksRepos,
      },
    }),
  };

  let variant: RepositoryPullRequestCreateFormVariant;
  if (from_branch == null) {
    variant = {
      state: PullRequestFormState.CONFIGURE,
      data: {
        sources,
        target: {
          branch: from_branch || Const.PRIMARY_BRANCH_REF,
          parentOrg,
          repo,
        },
      },
    };
  } else {
    let target: RepositoryPullRequestTarget = {
      branch: from_branch || Const.PRIMARY_BRANCH_REF,
      parentOrg,
      repo,
    };

    const orgRepoBranchRegExp = /^([\w\-_\.]+)\/([\w\-_\.]+)@(.*)/i;
    const matches = from_branch.match(orgRepoBranchRegExp);

    if (matches != null && Array.isArray(matches)) {
      const [_, targetOrgSlug, targetRepoSlug, targetFromBranch] = matches;
      const targetParentOrg = await orgService.getOrganizationBySlug(
        targetOrgSlug
      );
      const targetRepo = await repoService.getRepository(
        targetOrgSlug,
        targetRepoSlug
      );

      if (targetParentOrg == null || targetRepo == null) {
        return reply.status(404).callNotFound();
      }

      target = {
        branch: targetFromBranch,
        parentOrg: targetParentOrg,
        repo: targetRepo,
      };
    }

    variant = {
      state: PullRequestFormState.CONFIGURE,
      data: {
        sources,
        target,
      },
    };
  }

  const reqHandler = reply.makeRequestHandler(request, reply);
  return reqHandler<RepositoryPullRequestCreateViewProps>(
    RepositoryPullRequestCreateView.name,
    {
      errorMessage: null,
      initialValues: {},
      parentOrg,
      repo,
      variant,
    }
  );
};

export default getRepositoryPullRequestCreateView;