"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { actionClient } from "@/lib/action-client";
import { z } from "zod";

import { callApi } from "@/lib/api/server";

import {
  createIssueSchema,
  createLabelSchema,
  updateLabelSchema,
} from "./common";

const target = z.object({
  username: z.string(),
  repo: z.string(),
  number: z.number(),
});

function issuePath(username: string, repo: string, number?: number) {
  return number === undefined
    ? `/${username}/${repo}/issues`
    : `/${username}/${repo}/issues/${number}`;
}

export const createIssue = actionClient
  .inputSchema(
    createIssueSchema.extend({ username: z.string(), repo: z.string() }),
  )
  .action(
    async ({
      parsedInput: { username, repo, title, body, labels, assignees },
    }) => {
      const data = await callApi((client) =>
        client.POST("/api/repositories/{username}/{repo}/issues", {
          params: { path: { username, repo } },
          body: { title, body, labels, assignees },
        }),
      );

      redirect(`/${username}/${repo}/issues/${data.number}`);
    },
  );

export const updateIssue = actionClient
  .inputSchema(
    target.extend({
      title: z
        .string()
        .trim()
        .min(1, "Enter a title.")
        .max(200, "Titles are limited to 200 characters.")
        .optional(),
      body: z
        .string()
        .max(20000, "Descriptions are limited to 20000 characters.")
        .nullable()
        .optional(),
    }),
  )
  .action(async ({ parsedInput: { username, repo, number, title, body } }) => {
    await callApi((client) =>
      client.PATCH("/api/repositories/{username}/{repo}/issues/{number}", {
        params: { path: { username, repo, number } },
        body: { title, body },
      }),
    );

    revalidatePath(issuePath(username, repo, number));
  });

export const closeIssue = actionClient
  .inputSchema(target)
  .action(async ({ parsedInput: { username, repo, number } }) => {
    await callApi((client) =>
      client.POST("/api/repositories/{username}/{repo}/issues/{number}/close", {
        params: { path: { username, repo, number } },
      }),
    );

    revalidatePath(issuePath(username, repo, number));
    revalidatePath(issuePath(username, repo));
  });

export const reopenIssue = actionClient
  .inputSchema(target)
  .action(async ({ parsedInput: { username, repo, number } }) => {
    await callApi((client) =>
      client.POST(
        "/api/repositories/{username}/{repo}/issues/{number}/reopen",
        {
          params: { path: { username, repo, number } },
        },
      ),
    );

    revalidatePath(issuePath(username, repo, number));
    revalidatePath(issuePath(username, repo));
  });

export const commentOnIssue = actionClient
  .inputSchema(
    target.extend({
      body: z
        .string()
        .trim()
        .min(1, "Write something first.")
        .max(20000, "Comments are limited to 20000 characters."),
    }),
  )
  .action(async ({ parsedInput: { username, repo, number, body } }) => {
    const data = await callApi((client) =>
      client.POST(
        "/api/repositories/{username}/{repo}/issues/{number}/comments",
        {
          params: { path: { username, repo, number } },
          body: { body },
        },
      ),
    );

    revalidatePath(issuePath(username, repo, number));
    return data;
  });

export const updateIssueComment = actionClient
  .inputSchema(
    target.extend({
      commentId: z.string(),
      body: z
        .string()
        .trim()
        .min(1, "Write something first.")
        .max(20000, "Comments are limited to 20000 characters."),
    }),
  )
  .action(
    async ({ parsedInput: { username, repo, number, commentId, body } }) => {
      await callApi((client) =>
        client.PATCH(
          "/api/repositories/{username}/{repo}/issues/{number}/comments/{commentId}",
          {
            params: { path: { username, repo, number, commentId } },
            body: { body },
          },
        ),
      );

      revalidatePath(issuePath(username, repo, number));
    },
  );

export const deleteIssueComment = actionClient
  .inputSchema(target.extend({ commentId: z.string() }))
  .action(async ({ parsedInput: { username, repo, number, commentId } }) => {
    await callApi((client) =>
      client.DELETE(
        "/api/repositories/{username}/{repo}/issues/{number}/comments/{commentId}",
        {
          params: { path: { username, repo, number, commentId } },
        },
      ),
    );

    revalidatePath(issuePath(username, repo, number));
  });

export const setIssueLabels = actionClient
  .inputSchema(target.extend({ names: z.array(z.string()) }))
  .action(async ({ parsedInput: { username, repo, number, names } }) => {
    const data = await callApi((client) =>
      client.PUT("/api/repositories/{username}/{repo}/issues/{number}/labels", {
        params: { path: { username, repo, number } },
        body: { names },
      }),
    );

    revalidatePath(issuePath(username, repo, number));
    return data;
  });

export const setIssueAssignees = actionClient
  .inputSchema(target.extend({ usernames: z.array(z.string()) }))
  .action(async ({ parsedInput: { username, repo, number, usernames } }) => {
    const data = await callApi((client) =>
      client.PUT(
        "/api/repositories/{username}/{repo}/issues/{number}/assignees",
        {
          params: { path: { username, repo, number } },
          body: { usernames },
        },
      ),
    );

    revalidatePath(issuePath(username, repo, number));
    return data;
  });

export const createLabel = actionClient
  .inputSchema(
    createLabelSchema.extend({ username: z.string(), repo: z.string() }),
  )
  .action(
    async ({ parsedInput: { username, repo, name, description, color } }) => {
      const data = await callApi((client) =>
        client.POST("/api/repositories/{username}/{repo}/labels", {
          params: { path: { username, repo } },
          body: { name, description, color },
        }),
      );

      revalidatePath(`/${username}/${repo}/labels`);
      // A label change can surface on any issue in the repository.
      revalidatePath(issuePath(username, repo), "layout");
      return data;
    },
  );

export const updateLabel = actionClient
  .inputSchema(
    updateLabelSchema.extend({
      username: z.string(),
      repo: z.string(),
      labelId: z.string(),
    }),
  )
  .action(
    async ({
      parsedInput: { username, repo, labelId, name, description, color },
    }) => {
      const data = await callApi((client) =>
        client.PATCH("/api/repositories/{username}/{repo}/labels/{labelId}", {
          params: { path: { username, repo, labelId } },
          body: { name, description, color },
        }),
      );

      revalidatePath(`/${username}/${repo}/labels`);
      // A label change can surface on any issue in the repository.
      revalidatePath(issuePath(username, repo), "layout");
      return data;
    },
  );

export const deleteLabel = actionClient
  .inputSchema(
    z.object({ username: z.string(), repo: z.string(), labelId: z.string() }),
  )
  .action(async ({ parsedInput: { username, repo, labelId } }) => {
    await callApi((client) =>
      client.DELETE("/api/repositories/{username}/{repo}/labels/{labelId}", {
        params: { path: { username, repo, labelId } },
      }),
    );

    revalidatePath(`/${username}/${repo}/labels`);
    // A label change can surface on any issue in the repository.
    revalidatePath(issuePath(username, repo), "layout");
  });
