import { redirectToLoginAfterSessionExpired } from "@/features/auth/service/auth-service";

import type {
  CefpIssueMatrixRow,
  CefpIssueMatrixStatus,
  CefpIssueMatrixSummary,
} from "../components/cefp-issue-matrix-data";

/* -------------------------------------------------------------------------- */
/*                                   Types                                    */
/* -------------------------------------------------------------------------- */

type PaginationMeta = {
  page: number;
  limit: number;
  total: number;
  totalPages: number;
};

type ApiResponse<T> = {
  success: boolean;
  statusCode: number;
  message: string;
  data: T;
  meta?: PaginationMeta;
  timestamp?: string;
  path?: string;
};

type ApiLookup = {
  id: number;
  name: string;
};

type ApiIssueStatus = ApiLookup & {
  code: string;
};

type ApiAgency = ApiLookup & {
  logo: string | null;
};

type ApiIssueAttachment = {
  path: string;
  name: string;
  size: number;
  mimeType: string;
};


type ApiCefpIssueMatrixItem = {
  issueId: number;

  issueResolveId: number | null;
  meetingSummaryId: number | null;

  title: string;
  description: string;
  recommendation: string;

  category: ApiLookup;
  status: ApiIssueStatus;
  workingGroup: ApiLookup;

  primaryAgency: ApiAgency | null;

  rgcDecision: unknown;
  nextStep: unknown;
  remark?: string | null;

  escalation: boolean | null;

  submittedAt: string;
};

export type CefpStakeholderOption = {
  id: number;
  name: string;
  logo: string | null;
};

export type CefpIssueCategory = {
  id: number;
  name: string;
};

export type CefpWorkingGroup = {
  id: number;
  name: string;

  stakeholderTypeId: number;

  logo: string | null;
  description: string | null;
  coChair: string | null;

  relatedStakeholderId: number | null;

  active: boolean;

  createdAt: string;
  updatedAt: string;
};

export type CefpGovernmentAgency = {
  id: number;
  name: string;

  stakeholderTypeId: number;

  stakeholderType?: {
    id: number;
    name: string;
  };

  logo: string | null;
  description: string | null;
  coChair: string | null;

  relatedStakeholderId: number | null;

  active: boolean;

  createdAt: string;
  updatedAt: string;
};

export type CreateCefpIssueAgency = {
  stakeholderId: number;
  agencyOrder: number;
};

export type CreateCefpIssuePayload = {
  workingGroupId: number;

  title: string;
  description: string;
  recommendation: string;

  issueStatusId: number;
  categoryId: number;

  meetingRequestId?: number;

  attachment?: string;
  attachmentFile?: File | null;

  governmentAgencies: CreateCefpIssueAgency[];
};

export type CreatedCefpIssue = {
  id: number;

  title: string;
  description: string;
  recommendation: string;

  attachment:
  | ApiIssueAttachment
  | string
  | null;
  escalation: boolean | null;

  issueStatusId: number;
  categoryId: number;

  meetingRequestId: number | null;

  userId: number;
  stakeholderId: number;

  createdAt: string;
  updatedAt: string;
  deletedAt: string | null;

  issueStatus: {
    id: number;
    code: string;
    name: string;
  };

  category: {
    id: number;
    name: string;
  };

  stakeholder: {
    id: number;
    name: string;
    logo: string | null;

    stakeholderType: {
      id: number;
      name: string;
    };
  };

  governmentAgencies: Array<{
    agencyOrder: number;

    stakeholder: {
      id: number;
      name: string;
      description: string | null;
      logo: string | null;

      stakeholderType: {
        id: number;
        name: string;
      };
    };
  }>;
};

export type CreateCefpIssueResponse =
  ApiResponse<{
    issue: CreatedCefpIssue;
  }>;

export type UpdateCefpIssueEscalationResponse =
  ApiResponse<unknown>;

export type UpdateCefpIssueStatusResponse =
  ApiResponse<unknown>;


export type UpdateCefpIssueResponse =
  ApiResponse<unknown>;

export type DeleteCefpIssueResponse =
  ApiResponse<unknown>;

export type UpdateCefpIssuePayload = {
  title: string;
  description: string;
  recommendation: string;

  issueStatusId: number;
  categoryId: number;

  meetingRequestId?: number;

  attachment?: string;
  attachmentFile?: File | null;

  governmentAgencies: CreateCefpIssueAgency[];
};

export type CefpIssueAttachmentInfo = {
  path: string | null;
  attachmentUrl: string | null;
  fileName: string | null;
  size: number | null;
  mimeType: string | null;
};


export type CefpIssueEditAgency = {
  stakeholderId: number;
  agencyOrder: number;
};

export type CefpIssueEditInfo = {
  attachment: CefpIssueAttachmentInfo;
  governmentAgencies: CefpIssueEditAgency[];
};


/* -------------------------------------------------------------------------- */
/*                                Configuration                               */
/* -------------------------------------------------------------------------- */

const API_URL = (
  process.env.NEXT_PUBLIC_API_URL ||
  "http://localhost:3001/api/v1"
).replace(/\/+$/, "");

const BACKEND_ASSET_URL =
  API_URL.replace(
    /\/api(?:\/v\d+)?\/?$/,
    "",
  );

const FETCH_LIMIT = 50;

/* -------------------------------------------------------------------------- */
/*                                  Helpers                                   */
/* -------------------------------------------------------------------------- */

function resolveAssetUrl(
  value: string | null | undefined,
): string | null {
  if (!value) {
    return null;
  }

  if (
    value.startsWith("http://") ||
    value.startsWith("https://") ||
    value.startsWith("data:") ||
    value.startsWith("blob:")
  ) {
    return value;
  }

  if (value.startsWith("/")) {
    return `${BACKEND_ASSET_URL}${value}`;
  }

  return `${BACKEND_ASSET_URL}/${value}`;
}

function stripHtml(
  value: string,
): string {
  return value
    .replace(/<[^>]*>/g, " ")
    .replace(/&amp;/g, "&")
    .replace(/&nbsp;/g, " ")
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/\s+/g, " ")
    .trim();
}

function toPlainText(
  value: unknown,
): string {
  if (
    value === null ||
    value === undefined
  ) {
    return "-";
  }

  if (typeof value === "string") {
    return stripHtml(value) || "-";
  }

  if (
    typeof value === "number" ||
    typeof value === "boolean"
  ) {
    return String(value);
  }

  try {
    const serializedValue =
      JSON.stringify(value);

    return stripHtml(serializedValue) || "-";
  } catch {
    return "-";
  }
}

function normalizeStatus(
  status: ApiIssueStatus,
): CefpIssueMatrixStatus {
  /*
   * Backend status IDs used by Working Group Issue:
   * 1 = Draft
   * 2 = Not Addressed
   * 3 = Saved (backend code may still be NEW_SUBMISSION)
   * 4 = In Progress
   * 5 = Solved
   *
   * Use ID first so a different backend code/name spelling
   * cannot incorrectly fall back to Draft after refetch.
   */
  switch (status.id) {
    case 1:
      return "Draft";

    case 2:
      return "Not Addressed";

    case 3:
      return "Saved";

    case 4:
      return "In Progress";

    case 5:
      return "Solved";
  }

  const normalizedCode = String(status.code ?? "")
    .trim()
    .toUpperCase()
    .replace(/[\s-]+/g, "_");

  switch (normalizedCode) {
    case "DRAFT":
      return "Draft";

    case "NEW_SUBMISSION":
    case "NEW":
    case "SAVED":
      return "Saved";

    case "IN_PROGRESS":
    case "INPROGRESS":
      return "In Progress";

    case "SOLVED":
    case "RESOLVED":
      return "Solved";

    case "NOT_ADDRESSED":
    case "NOT_ADDRESS":
    case "NOTADDRESSED":
      return "Not Addressed";
  }

  const normalizedName = String(status.name ?? "")
    .trim()
    .toLowerCase()
    .replace(/[\s_-]+/g, " ");

  if (
    normalizedName === "not addressed" ||
    normalizedName === "not address"
  ) {
    return "Not Addressed";
  }

  if (normalizedName === "in progress") {
    return "In Progress";
  }

  if (
    normalizedName === "solved" ||
    normalizedName === "resolved"
  ) {
    return "Solved";
  }

  if (
    normalizedName === "new submission" ||
    normalizedName === "new" ||
    normalizedName === "saved"
  ) {
    return "Saved";
  }

  return "Draft";
}

function getYear(
  value: string,
): string {
  const date =
    new Date(value);

  return Number.isNaN(
    date.getTime(),
  )
    ? "-"
    : String(
      date.getFullYear(),
    );
}

function mapCefpIssueMatrixRow(
  item: ApiCefpIssueMatrixItem,
): CefpIssueMatrixRow {
  return {
    id:
      item.issueId,

    issueId:
      item.issueId,

    issueResolveId:
      item.issueResolveId,

    meetingSummaryId:
      item.meetingSummaryId,

    workingGroupId:
      item.workingGroup.id,

    workingGroup:
      item.workingGroup.name.trim() ||
      "-",

    issue:
      item.title.trim() ||
      "-",

    category:
      item.category.name.trim() ||
      "-",

    issueDescription:
      stripHtml(item.description) ||
      "-",

    recommendation:
      stripHtml(item.recommendation) ||
      "-",

    governmentDecision:
      toPlainText(
        item.rgcDecision,
      ),

    primaryAgency:
      item.primaryAgency?.name.trim() ||
      "-",

    primaryAgencyImage:
      resolveAssetUrl(
        item.primaryAgency?.logo,
      ),

    agencies: item.primaryAgency
      ? [
        {
          id:
            item.primaryAgency.id,

          name:
            item.primaryAgency.name.trim() ||
            "-",

          logo:
            resolveAssetUrl(
              item.primaryAgency.logo,
            ),
        },
      ]
      : [],

    plenaryEscalation:
      item.escalation === true,

    status:
      normalizeStatus(
        item.status,
      ),

    statusId:
      item.status.id,

    nextStep:
      toPlainText(
        item.nextStep,
      ),

    remark:
      item.remark ?? null,

    submittedAt:
      item.submittedAt,

    year:
      getYear(
        item.submittedAt,
      ),

    /*
     * Ministry-submitted issues come from a meeting summary.
     * CEFP can only view those issues.
     * CEFP-created issues have no meetingSummaryId and can be managed.
     */
    isMinistrySubmitted:
      item.meetingSummaryId !== null,

    canManage:
      item.meetingSummaryId === null,
  };
}

function normalizeIssueAttachment(
  value: unknown,
): CefpIssueAttachmentInfo {
  if (!value) {
    return {
      path: null,
      attachmentUrl: null,
      fileName: null,
      size: null,
      mimeType: null,
    };
  }

  if (
    typeof value === "string"
  ) {
    const path =
      value.trim() || null;

    const rawFileName =
      path
        ?.split("?")[0]
        .split("/")
        .pop() ??
      null;

    let fileName =
      rawFileName;

    if (rawFileName) {
      try {
        fileName =
          decodeURIComponent(
            rawFileName,
          );
      } catch {
        fileName =
          rawFileName;
      }
    }

    return {
      path,
      attachmentUrl:
        resolveAssetUrl(
          path,
        ),
      fileName,
      size: null,
      mimeType: null,
    };
  }

  if (
    typeof value ===
    "object"
  ) {
    const attachment =
      value as Partial<ApiIssueAttachment>;

    const path =
      typeof attachment.path ===
        "string"
        ? attachment.path.trim() ||
        null
        : null;

    const fileName =
      typeof attachment.name ===
        "string" &&
        attachment.name.trim()
        ? attachment.name.trim()
        : path
          ?.split("?")[0]
          .split("/")
          .pop() ??
        null;

    return {
      path,

      attachmentUrl:
        resolveAssetUrl(
          path,
        ),

      fileName,

      size:
        typeof attachment.size ===
          "number" &&
          Number.isFinite(
            attachment.size,
          )
          ? attachment.size
          : null,

      mimeType:
        typeof attachment.mimeType ===
          "string"
          ? attachment.mimeType.trim() ||
          null
          : null,
    };
  }

  return {
    path: null,
    attachmentUrl: null,
    fileName: null,
    size: null,
    mimeType: null,
  };
}

/* -------------------------------------------------------------------------- */
/*                              Request Helpers                               */
/* -------------------------------------------------------------------------- */

function getApiErrorMessage(
  body: unknown,
  status: number,
): string {
  if (
    body &&
    typeof body === "object"
  ) {
    const apiBody = body as {
      message?: unknown;
      errors?: unknown;
    };

    if (Array.isArray(apiBody.errors)) {
      const errors = apiBody.errors
        .filter(
          (item): item is string =>
            typeof item === "string" &&
            item.trim() !== "",
        )
        .map((item) => item.trim());

      if (errors.length > 0) {
        return errors.join(" ");
      }
    }

    const message = apiBody.message;

    if (Array.isArray(message)) {
      const messages = message
        .filter(
          (item): item is string =>
            typeof item === "string" &&
            item.trim() !== "",
        )
        .map((item) => item.trim());

      if (messages.length > 0) {
        return messages.join(" ");
      }
    }

    if (
      typeof message === "string" &&
      message.trim()
    ) {
      return message.trim();
    }
  }

  switch (status) {
    case 400:
      return "Invalid request data.";

    case 401:
      return "Your session has expired.";

    case 403:
      return "You do not have permission to perform this action.";

    case 404:
      return "Requested resource was not found.";

    case 413:
      return "The selected attachment is too large.";

    default:
      return `Request failed (${status}).`;
  }
}

async function parseResponseBody(
  response: Response,
): Promise<unknown> {
  const text =
    await response.text();

  if (!text) {
    return null;
  }

  try {
    return JSON.parse(text) as unknown;
  } catch {
    return text;
  }
}

async function handleFailedResponse(
  response: Response,
  responseBody: unknown,
): Promise<never> {
  if (response.status === 401) {
    redirectToLoginAfterSessionExpired();
  }

  throw new Error(
    getApiErrorMessage(
      responseBody,
      response.status,
    ),
  );
}

async function getRequest<T>(
  path: string,
): Promise<T> {
  const response =
    await fetch(
      `${API_URL}${path}`,
      {
        method: "GET",

        credentials:
          "include",

        cache:
          "no-store",

        headers: {
          Accept:
            "application/json",
        },
      },
    );

  const responseBody =
    await parseResponseBody(
      response,
    );

  if (!response.ok) {
    return handleFailedResponse(
      response,
      responseBody,
    );
  }

  return responseBody as T;
}

async function postFormDataRequest<T>(
  path: string,
  formData: FormData,
): Promise<T> {
  const response =
    await fetch(
      `${API_URL}${path}`,
      {
        method: "POST",

        credentials:
          "include",

        headers: {
          Accept:
            "application/json",
        },

        body:
          formData,
      },
    );

  const responseBody =
    await parseResponseBody(
      response,
    );

  if (!response.ok) {
    return handleFailedResponse(
      response,
      responseBody,
    );
  }

  return responseBody as T;
}

async function patchJsonRequest<T>(
  path: string,
  body: Record<
    string,
    unknown
  >,
): Promise<T> {
  const response =
    await fetch(
      `${API_URL}${path}`,
      {
        method: "PATCH",

        credentials:
          "include",

        cache:
          "no-store",

        headers: {
          Accept:
            "application/json",

          "Content-Type":
            "application/json",
        },

        body:
          JSON.stringify(body),
      },
    );

  const responseBody =
    await parseResponseBody(
      response,
    );

  if (!response.ok) {
    return handleFailedResponse(
      response,
      responseBody,
    );
  }

  return responseBody as T;
}

async function patchFormDataRequest<T>(
  path: string,
  formData: FormData,
): Promise<T> {
  const response =
    await fetch(
      `${API_URL}${path}`,
      {
        method: "PATCH",
        credentials: "include",
        cache: "no-store",
        headers: {
          Accept: "application/json",
        },
        body: formData,
      },
    );

  const responseBody =
    await parseResponseBody(
      response,
    );

  if (!response.ok) {
    return handleFailedResponse(
      response,
      responseBody,
    );
  }

  return responseBody as T;
}

async function deleteRequest<T>(
  path: string,
): Promise<T> {
  const response =
    await fetch(
      `${API_URL}${path}`,
      {
        method: "DELETE",
        credentials: "include",
        cache: "no-store",
        headers: {
          Accept: "application/json",
        },
      },
    );

  const responseBody =
    await parseResponseBody(
      response,
    );

  if (!response.ok) {
    return handleFailedResponse(
      response,
      responseBody,
    );
  }

  return responseBody as T;
}

/* -------------------------------------------------------------------------- */
/*                               Issue Matrix                                 */
/* -------------------------------------------------------------------------- */

async function getCefpIssueMatrixPage(
  page: number,
): Promise<
  ApiResponse<
    ApiCefpIssueMatrixItem[]
  >
> {
  return getRequest<
    ApiResponse<
      ApiCefpIssueMatrixItem[]
    >
  >(
    `/issues?page=${page}&limit=${FETCH_LIMIT}`,
  );
}

export async function getCefpIssueMatrixIssues(): Promise<
  CefpIssueMatrixRow[]
> {
  const firstPage =
    await getCefpIssueMatrixPage(
      1,
    );

  const totalPages =
    firstPage.meta?.totalPages ??
    1;

  if (totalPages <= 1) {
    return (
      firstPage.data ?? []
    ).map(
      mapCefpIssueMatrixRow,
    );
  }

  const remainingPageNumbers =
    Array.from(
      {
        length:
          totalPages - 1,
      },
      (
        _value,
        index,
      ) =>
        index + 2,
    );

  const remainingPages =
    await Promise.all(
      remainingPageNumbers.map(
        (page) =>
          getCefpIssueMatrixPage(
            page,
          ),
      ),
    );

  const allItems = [
    ...(firstPage.data ?? []),

    ...remainingPages.flatMap(
      (response) =>
        response.data ?? [],
    ),
  ];

  return allItems.map(
    mapCefpIssueMatrixRow,
  );
}

export async function getCefpIssueEditInfo(
  issueId: number,
): Promise<CefpIssueEditInfo> {
  if (
    !Number.isInteger(issueId) ||
    issueId <= 0
  ) {
    throw new Error("Invalid issue ID.");
  }

  const response =
    await getRequest<
      ApiResponse<{
        attachment?:
        | ApiIssueAttachment
        | string
        | null;

        governmentAgencies?: Array<{
          agencyOrder?: number;
          stakeholderId?: number;

          stakeholder?: {
            id?: number;
            name?: string;
            logo?: string | null;
          } | null;
        }>;
      }>
    >(`/issues/${issueId}`);

  const rawAgencies =
    Array.isArray(
      response.data?.governmentAgencies,
    )
      ? response.data?.governmentAgencies ?? []
      : [];

  const governmentAgencies =
    rawAgencies
      .map(
        (
          agency,
          index,
        ): CefpIssueEditAgency | null => {
          const stakeholderId =
            typeof agency.stakeholderId === "number"
              ? agency.stakeholderId
              : typeof agency.stakeholder?.id === "number"
                ? agency.stakeholder.id
                : null;

          if (
            stakeholderId === null ||
            stakeholderId <= 0
          ) {
            return null;
          }

          const agencyOrder =
            typeof agency.agencyOrder === "number" &&
              agency.agencyOrder > 0
              ? agency.agencyOrder
              : index + 1;

          return {
            stakeholderId,
            agencyOrder,
          };
        },
      )
      .filter(
        (
          agency,
        ): agency is CefpIssueEditAgency =>
          agency !== null,
      )
      .sort(
        (a, b) =>
          a.agencyOrder -
          b.agencyOrder,
      );

  return {
    attachment:
      normalizeIssueAttachment(
        response.data?.attachment,
      ),

    governmentAgencies,
  };
}

export async function getCefpIssueAttachment(
  issueId: number,
): Promise<CefpIssueAttachmentInfo> {
  if (
    !Number.isInteger(
      issueId,
    ) ||
    issueId <= 0
  ) {
    throw new Error(
      "Invalid issue ID.",
    );
  }

  const response =
    await getRequest<
      ApiResponse<{
        attachment?:
        | ApiIssueAttachment
        | string
        | null;
      }>
    >(
      `/issues/${issueId}`,
    );

  return normalizeIssueAttachment(
    response.data?.attachment,
  );
}

export async function getCefpIssueMatrixSummary(): Promise<CefpIssueMatrixSummary> {
  const response =
    await getRequest<
      ApiResponse<CefpIssueMatrixSummary>
    >(
      "/issues/summary",
    );

  return response.data;
}

/* -------------------------------------------------------------------------- */
/*                               Working Groups                               */
/* -------------------------------------------------------------------------- */

export async function getCefpWorkingGroups(): Promise<
  CefpStakeholderOption[]
> {
  const response =
    await getRequest<
      ApiResponse<
        CefpWorkingGroup[]
      >
    >(
      "/stakeholders/working-groups",
    );

  if (
    !Array.isArray(
      response.data,
    )
  ) {
    return [];
  }

  return response.data
    .filter(
      (item) =>
        typeof item.id ===
        "number" &&
        typeof item.name ===
        "string" &&
        item.active !== false,
    )
    .map((item) => ({
      id:
        item.id,

      name:
        item.name.trim(),

      logo:
        resolveAssetUrl(
          item.logo,
        ),
    }))
    .filter(
      (item) =>
        item.name !== "",
    )
    .sort(
      (
        firstItem,
        secondItem,
      ) =>
        firstItem.name.localeCompare(
          secondItem.name,
        ),
    );
}

/* -------------------------------------------------------------------------- */
/*                            Government Agencies                             */
/* -------------------------------------------------------------------------- */

export async function getCefpGovernmentAgencies(): Promise<
  CefpStakeholderOption[]
> {
  const response =
    await getRequest<
      ApiResponse<
        CefpGovernmentAgency[]
      >
    >(
      `/stakeholders?page=1&limit=${FETCH_LIMIT}&stakeholderTypeId=1`,
    );

  if (
    !Array.isArray(
      response.data,
    )
  ) {
    return [];
  }

  return response.data
    .filter(
      (item) =>
        typeof item.id ===
        "number" &&
        typeof item.name ===
        "string" &&
        item.stakeholderTypeId ===
        1 &&
        item.active !== false,
    )
    .map((item) => ({
      id:
        item.id,

      name:
        item.name.trim(),

      logo:
        resolveAssetUrl(
          item.logo,
        ),
    }))
    .filter(
      (item) =>
        item.name !== "",
    )
    .sort(
      (
        firstItem,
        secondItem,
      ) =>
        firstItem.name.localeCompare(
          secondItem.name,
        ),
    );
}

/* -------------------------------------------------------------------------- */
/*                              Issue Categories                              */
/* -------------------------------------------------------------------------- */

export async function getCefpIssueCategories(): Promise<
  CefpIssueCategory[]
> {
  const response =
    await getRequest<
      ApiResponse<
        CefpIssueCategory[]
      >
    >(
      "/working-group-issues/categories",
    );

  if (
    !Array.isArray(
      response.data,
    )
  ) {
    return [];
  }

  return response.data
    .filter(
      (category) =>
        Number.isInteger(
          category.id,
        ) &&
        category.id > 0 &&
        typeof category.name ===
        "string" &&
        category.name.trim() !==
        "",
    )
    .map((category) => ({
      id:
        category.id,

      name:
        category.name.trim(),
    }))
    .sort(
      (
        firstCategory,
        secondCategory,
      ) =>
        firstCategory.name.localeCompare(
          secondCategory.name,
        ),
    );
}

/* -------------------------------------------------------------------------- */
/*                                Create Issue                                */
/* -------------------------------------------------------------------------- */

export async function createCefpIssue(
  payload: CreateCefpIssuePayload,
): Promise<CreateCefpIssueResponse> {
  if (
    !Number.isInteger(
      payload.workingGroupId,
    ) ||
    payload.workingGroupId <= 0
  ) {
    throw new Error(
      "Invalid working group ID.",
    );
  }

  if (
    !Number.isInteger(
      payload.categoryId,
    ) ||
    payload.categoryId <= 0
  ) {
    throw new Error(
      "Invalid issue category ID.",
    );
  }

  if (
    !Number.isInteger(
      payload.issueStatusId,
    ) ||
    payload.issueStatusId <= 0
  ) {
    throw new Error(
      "Invalid issue status ID.",
    );
  }

  if (!payload.title.trim()) {
    throw new Error(
      "Issue title is required.",
    );
  }

  if (!payload.description.trim()) {
    throw new Error(
      "Issue description is required.",
    );
  }

  if (
    !payload.recommendation.trim()
  ) {
    throw new Error(
      "Issue recommendation is required.",
    );
  }

  if (
    payload.governmentAgencies.length ===
    0
  ) {
    throw new Error(
      "At least one government agency is required.",
    );
  }

  const formData =
    new FormData();

  formData.append(
    "workingGroupId",
    String(
      payload.workingGroupId,
    ),
  );

  formData.append(
    "title",
    payload.title.trim(),
  );

  formData.append(
    "description",
    payload.description.trim(),
  );

  formData.append(
    "recommendation",
    payload.recommendation.trim(),
  );

  formData.append(
    "issueStatusId",
    String(
      payload.issueStatusId,
    ),
  );

  formData.append(
    "categoryId",
    String(
      payload.categoryId,
    ),
  );

  if (
    payload.meetingRequestId !==
    undefined
  ) {
    formData.append(
      "meetingRequestId",
      String(
        payload.meetingRequestId,
      ),
    );
  }

  if (
    payload.attachment?.trim()
  ) {
    formData.append(
      "attachment",
      payload.attachment.trim(),
    );
  }

  formData.append(
    "governmentAgencies",
    JSON.stringify(
      payload.governmentAgencies,
    ),
  );

  if (payload.attachmentFile) {
    formData.append(
      "attachmentFile",
      payload.attachmentFile,
      payload.attachmentFile.name,
    );
  }

  return postFormDataRequest<CreateCefpIssueResponse>(
    "/issues",
    formData,
  );
}

/* -------------------------------------------------------------------------- */
/*                         Update Plenary Escalation                          */
/* -------------------------------------------------------------------------- */

export async function updateCefpIssueEscalation(
  issueId: number,
  escalation: boolean,
): Promise<UpdateCefpIssueEscalationResponse> {
  if (
    !Number.isInteger(
      issueId,
    ) ||
    issueId <= 0
  ) {
    throw new Error(
      "Invalid issue ID.",
    );
  }

  return patchJsonRequest<UpdateCefpIssueEscalationResponse>(
    `/issues/${issueId}`,
    {
      escalation,
    },
  );
}

/* -------------------------------------------------------------------------- */
/*                              Update Status                                 */
/* -------------------------------------------------------------------------- */

export async function updateCefpIssueStatus(
  issueId: number,
  issueStatusId: number,
): Promise<UpdateCefpIssueStatusResponse> {
  if (
    !Number.isInteger(
      issueId,
    ) ||
    issueId <= 0
  ) {
    throw new Error(
      "Invalid issue ID.",
    );
  }

  if (
    !Number.isInteger(
      issueStatusId,
    ) ||
    issueStatusId <= 0
  ) {
    throw new Error(
      "Invalid issue status ID.",
    );
  }

  return patchJsonRequest<UpdateCefpIssueStatusResponse>(
    `/issues/${issueId}`,
    {
      issueStatusId,
    },
  );
}

/* -------------------------------------------------------------------------- */
/*                              Update Issue                                  */
/* -------------------------------------------------------------------------- */

export async function updateCefpIssue(
  issueId: number,
  payload: UpdateCefpIssuePayload,
): Promise<UpdateCefpIssueResponse> {
  if (!Number.isInteger(issueId) || issueId <= 0) {
    throw new Error("Invalid issue ID.");
  }

  if (
    !Number.isInteger(payload.categoryId) ||
    payload.categoryId <= 0
  ) {
    throw new Error("Invalid issue category ID.");
  }

  if (
    !Number.isInteger(payload.issueStatusId) ||
    payload.issueStatusId <= 0
  ) {
    throw new Error("Invalid issue status ID.");
  }

  if (!payload.title.trim()) {
    throw new Error("Issue title is required.");
  }

  if (!payload.description.trim()) {
    throw new Error("Issue description is required.");
  }

  if (!payload.recommendation.trim()) {
    throw new Error("Issue recommendation is required.");
  }

  if (payload.governmentAgencies.length === 0) {
    throw new Error(
      "At least one government agency is required.",
    );
  }

  /*
   * IMPORTANT:
   * The PATCH /issues/:id endpoint accepts normal JSON for issue updates.
   * Sending FormData when there is no new file turns number/array fields
   * into strings and can make NestJS validation return 400 Bad Request.
   *
   * Therefore:
   * - no new file  -> PATCH JSON
   * - new file     -> PATCH multipart/form-data
   */
  if (!payload.attachmentFile) {
    return patchJsonRequest<UpdateCefpIssueResponse>(
      `/issues/${issueId}`,
      {
        // Do NOT send workingGroupId on PATCH /issues/:id.
        // Backend UpdateIssueDto does not allow this property
        // ("property workingGroupId should not exist").
        title:
          payload.title.trim(),

        description:
          payload.description.trim(),

        recommendation:
          payload.recommendation.trim(),

        issueStatusId:
          payload.issueStatusId,

        categoryId:
          payload.categoryId,

        governmentAgencies:
          payload.governmentAgencies,

        ...(payload.meetingRequestId !== undefined
          ? {
            meetingRequestId:
              payload.meetingRequestId,
          }
          : {}),

        ...(payload.attachment?.trim()
          ? {
            attachment:
              payload.attachment.trim(),
          }
          : {}),
      },
    );
  }

  const formData = new FormData();

  formData.append(
    "title",
    payload.title.trim(),
  );

  formData.append(
    "description",
    payload.description.trim(),
  );

  formData.append(
    "recommendation",
    payload.recommendation.trim(),
  );

  formData.append(
    "issueStatusId",
    String(payload.issueStatusId),
  );

  formData.append(
    "categoryId",
    String(payload.categoryId),
  );

  formData.append(
    "governmentAgencies",
    JSON.stringify(
      payload.governmentAgencies,
    ),
  );

  if (
    payload.meetingRequestId !== undefined
  ) {
    formData.append(
      "meetingRequestId",
      String(
        payload.meetingRequestId,
      ),
    );
  }

  if (payload.attachment?.trim()) {
    formData.append(
      "attachment",
      payload.attachment.trim(),
    );
  }

  formData.append(
    "attachmentFile",
    payload.attachmentFile,
    payload.attachmentFile.name,
  );

  return patchFormDataRequest<UpdateCefpIssueResponse>(
    `/issues/${issueId}`,
    formData,
  );
}

/* -------------------------------------------------------------------------- */
/*                              Delete Issue                                  */
/* -------------------------------------------------------------------------- */

export async function deleteCefpIssue(
  issueId: number,
): Promise<DeleteCefpIssueResponse> {
  if (!Number.isInteger(issueId) || issueId <= 0) {
    throw new Error("Invalid issue ID.");
  }

  return deleteRequest<DeleteCefpIssueResponse>(
    `/issues/${issueId}`,
  );
}