"use server";

// import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
// import { BASE_API_URL } from "@/config/api";
import {
  serverFetch,
  serverFetchJson,
  serverFetchWithNoContent,
  ServerFetchError,
} from "../../utils/fetchUtil";
import { BASE_API_URL } from "../../../config/api";
import { revalidateTag } from "next/cache";
import { cache } from "react";


export interface ShopAndTruck{
  id: number;
  name: String;
  code: String;
  addr1: String;
  addr2: String;
  addr3: String;
  contactNo: number;  
  type: String;
  contactEmail: String;
  contactName: String;
  truckLanceCode: String;
  DepartureTime: String;
  LoadingSequence?: number | null;
  districtReference: string | null;
  Store_id: Number;
  remark?: String | null;
  truckId?: number;
}

export interface Shop{
  id: number;
  name: String;
  code: String;
  addr3: String;
}

export interface Truck{
  id?: number;
  truckLanceCode: String;
  departureTime: String | number[];
  loadingSequence: number;
  districtReference: string | null;
  storeId: Number | String;
  remark?: String | null;
  shopName?: String | null;
  shopCode?: String | null;
}

export interface SaveTruckLane {
  id: number;
  truckLanceCode: string;
  departureTime: string;
  loadingSequence: number;
  districtReference: string | null;
  storeId: string;
  remark?: string | null;
  logisticId?: number | null;
  /** When true, set truck.logistic to logisticId (null clears). When false/omit, do not change logistic. */
  updateLogistic?: boolean;
}

/** POST /truck/updateLaneLogistic — 同線桶內 truck 列一次更新 logistic（單一 transaction） */
export interface UpdateLaneLogisticRequest {
  truckLanceCode: string;
  remark?: string | null;
  logisticId?: number | null;
}

export interface DeleteTruckLane {
  id: number;
}

export interface UpdateTruckShopDetailsRequest {
  id: number;
  shopId?: number | null;
  shopName: string | null;
  shopCode: string | null;
  loadingSequence: number;
  remark?: string | null;
}

export interface SaveTruckRequest {
  id?: number | null;
  store_id: string;
  truckLanceCode: string;
  departureTime: string;
  shopId: number;
  shopName: string;
  shopCode: string;
  loadingSequence: number;
  districtReference?: string | null;
  remark?: string | null;
  logisticId?: number | null;
}

export interface CreateTruckWithoutShopRequest {
  store_id: string;
  truckLanceCode: string;
  departureTime: string;
  loadingSequence?: number;
  districtReference?: string | null;
  logisticId?: number | null;
  remark?: string | null;
}

export interface MessageResponse {
  id: number | null;
  name: string | null;
  code: string | null;
  type: string;
  message: string;
  errorPosition: string | null;
  entity: Truck | null;
}

export const fetchAllShopsAction = cache(async (params?: Record<string, string | number | null>) => {
  const endpoint = `${BASE_API_URL}/shop/combo/allShop`;
  const qs = params
    ? Object.entries(params)
        .filter(([, v]) => v !== null && v !== undefined && String(v).trim() !== "")
        .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
        .join("&")
    : "";

  const url = qs ? `${endpoint}?${qs}` : endpoint;

  return serverFetchJson<ShopAndTruck[]>(url, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const findTruckLaneByShopIdAction = cache(async (shopId: number | string) => {
  const endpoint = `${BASE_API_URL}/truck/findTruckLane/${shopId}`;
  
  return serverFetchJson<Truck[]>(endpoint, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const updateTruckLaneAction = async (data: SaveTruckLane) => {
  const endpoint = `${BASE_API_URL}/truck/updateTruckLane`;
  
  return serverFetchJson<MessageResponse>(endpoint, {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
};

export const updateLaneLogisticAction = async (
  data: UpdateLaneLogisticRequest,
): Promise<MessageResponse> => {
  const endpoint = `${BASE_API_URL}/truck/updateLaneLogistic`;
  return serverFetchJson<MessageResponse>(endpoint, {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
};

export const deleteTruckLaneAction = async (data: DeleteTruckLane) => {
  const endpoint = `${BASE_API_URL}/truck/deleteTruckLane`;
  
  return serverFetchJson<MessageResponse>(endpoint, {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
};

export const createTruckAction = async (data: SaveTruckRequest) => {
  const endpoint = `${BASE_API_URL}/truck/createTruckInShop`;
  
  return serverFetchJson<MessageResponse>(endpoint, {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
};

export const findAllUniqueTruckLaneCombinationsAction = cache(async () => {
  const endpoint = `${BASE_API_URL}/truck/findAllUniqueTruckLanceCodeAndRemarkCombinations`;
  
  return serverFetchJson<Truck[]>(endpoint, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

/** O(1) 取整個 RouteBoard 所需 truck rows；前端自行按 (truckLanceCode, remark) 分桶。 */
export const findAllForRouteBoardAction = cache(async () => {
  const endpoint = `${BASE_API_URL}/truck/findAllForRouteBoard`;
  return serverFetchJson<Truck[]>(endpoint, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const findAllShopsByTruckLanceCodeAndRemarkAction = cache(async (truckLanceCode: string, remark: string) => {
  const endpoint = `${BASE_API_URL}/truck/findAllFromShopAndTruckByTruckLanceCodeAndRemarkAndDeletedFalse`;
  const url = `${endpoint}?truckLanceCode=${encodeURIComponent(truckLanceCode)}&remark=${encodeURIComponent(remark)}`;
  
  return serverFetchJson<ShopAndTruck[]>(url, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const findAllShopsByTruckLanceCodeAction = cache(async (truckLanceCode: string) => {
  const endpoint = `${BASE_API_URL}/truck/findAllFromShopAndTruckByTruckLanceCodeAndDeletedFalse`;
  const url = `${endpoint}?truckLanceCode=${encodeURIComponent(truckLanceCode)}`;
  
  return serverFetchJson<ShopAndTruck[]>(url, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const findAllByTruckLanceCodeAndDeletedFalseAction = cache(async (truckLanceCode: string) => {
  const endpoint = `${BASE_API_URL}/truck/findAllByTruckLanceCodeAndDeletedFalse`;
  const url = `${endpoint}?truckLanceCode=${encodeURIComponent(truckLanceCode)}`;
  
  return serverFetchJson<Truck[]>(url, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

/** 與 `findAllUniqueTruckLanceCodeAndRemarkCombinations` 同一 (code, remark) 桶；remark 空則不帶參數。 */
export const findAllByTruckLanceCodeAndRemarkAndDeletedFalseAction = cache(
  async (truckLanceCode: string, remark: string | null | undefined) => {
    const endpoint = `${BASE_API_URL}/truck/findAllByTruckLanceCodeAndRemarkAndDeletedFalse`;
    const params = new URLSearchParams();
    params.set("truckLanceCode", truckLanceCode);
    const r = remark != null && String(remark).trim() !== "" ? String(remark).trim() : "";
    if (r !== "") params.set("remark", r);
    const url = `${endpoint}?${params.toString()}`;
    return serverFetchJson<Truck[]>(url, {
      method: "GET",
      headers: { "Content-Type": "application/json" },
    });
  },
);

export const updateTruckShopDetailsAction = async (data: UpdateTruckShopDetailsRequest) => {
  const endpoint = `${BASE_API_URL}/truck/updateTruckShopDetails`;
  
  return serverFetchJson<MessageResponse>(endpoint, {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
};

export const createTruckWithoutShopAction = async (data: CreateTruckWithoutShopRequest) => {
  const endpoint = `${BASE_API_URL}/truck/createTruckWithoutShop`;
  
  return serverFetchJson<MessageResponse>(endpoint, {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
};

/** PDF 圖1：每車線一個 worksheet（MTMS_ROUTE_V1）。回傳 base64 方便 client 下載。 */
export const exportRouteLanesExcelAction = async (
  laneIds: string[],
): Promise<{ base64: string; filename: string }> => {
  const response = await serverFetch(`${BASE_API_URL}/truck/exportRouteLanesExcel`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept:
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    },
    body: JSON.stringify({ laneIds }),
  });
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new ServerFetchError(
      `Export failed: ${response.status} ${text}`.trim(),
      response,
    );
  }
  const cd = response.headers.get("content-disposition") ?? "";
  let filename = `MTMS_車線_${Date.now()}.xlsx`;
  const quoted = /filename="([^"]+)"/i.exec(cd);
  const star = /filename\*=UTF-8''([^;\s]+)/i.exec(cd);
  const raw = (star?.[1] || quoted?.[1])?.trim();
  if (raw) {
    try {
      filename = decodeURIComponent(raw);
    } catch {
      filename = raw;
    }
  }
  const buf = await response.arrayBuffer();
  return {
    base64: Buffer.from(buf).toString("base64"),
    filename,
  };
};

/** 圖2：車線 Report（單一 sheet；每間物流公司一個水平區塊）。 */
export const exportRouteReportExcelAction = async (
  laneIds: string[],
): Promise<{ base64: string; filename: string }> => {
  const response = await serverFetch(`${BASE_API_URL}/truck/exportRouteReportExcel`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept:
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    },
    body: JSON.stringify({ laneIds }),
  });
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new ServerFetchError(
      `Export failed: ${response.status} ${text}`.trim(),
      response,
    );
  }
  const cd = response.headers.get("content-disposition") ?? "";
  let filename = `車線Report_${Date.now()}.xlsx`;
  const quoted = /filename="([^"]+)"/i.exec(cd);
  const star = /filename\*=UTF-8''([^;\s]+)/i.exec(cd);
  const raw = (star?.[1] || quoted?.[1])?.trim();
  if (raw) {
    try {
      filename = decodeURIComponent(raw);
    } catch {
      filename = raw;
    }
  }
  const buf = await response.arrayBuffer();
  return {
    base64: Buffer.from(buf).toString("base64"),
    filename,
  };
};

export const exportTruckLaneVersionReportExcelAction = async (
  fromVersionId: number,
  toVersionId: number,
): Promise<{ base64: string; filename: string }> => {
  const response = await serverFetch(
    `${BASE_API_URL}/truck/exportTruckLaneVersionReportExcel`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept:
          "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      },
      body: JSON.stringify({ fromVersionId, toVersionId }),
    },
  );
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new ServerFetchError(
      `Export failed: ${response.status} ${text}`.trim(),
      response,
    );
  }
  const cd = response.headers.get("content-disposition") ?? "";
  let filename = `車線版本報告_${Date.now()}.xlsx`;
  const quoted = /filename="([^"]+)"/i.exec(cd);
  const star = /filename\*=UTF-8''([^;\s]+)/i.exec(cd);
  const raw = (star?.[1] || quoted?.[1])?.trim();
  if (raw) {
    try {
      filename = decodeURIComponent(raw);
    } catch {
      filename = raw;
    }
  }
  const buf = await response.arrayBuffer();
  return {
    base64: Buffer.from(buf).toString("base64"),
    filename,
  };
};

export const importRouteLanesExcelAction = async (
  formData: FormData,
): Promise<MessageResponse> => {
  const response = await serverFetch(`${BASE_API_URL}/truck/importRouteLanesExcel`, {
    method: "POST",
    body: formData,
  });
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new ServerFetchError(
      `Import failed: ${response.status} ${text}`.trim(),
      response,
    );
  }
  return (await response.json()) as MessageResponse;
};

export type RouteLaneImportPreviewRow = {
  truckRowId: number | null;
  truckLanceCode: string;
  remark: string | null;
  storeId: string;
  departureTime: string;
  shopId: number;
  shopName: string;
  shopCode: string;
  loadingSequence: number;
  districtReference: string | null;
  logisticId: number | null;
};

export type ParseRouteLanesExcelResponse = {
  sheetCount: number;
  rowCount: number;
  rows: RouteLaneImportPreviewRow[];
};

export const parseRouteLanesExcelAction = async (
  formData: FormData,
): Promise<ParseRouteLanesExcelResponse> => {
  const response = await serverFetch(`${BASE_API_URL}/truck/parseRouteLanesExcel`, {
    method: "POST",
    body: formData,
  });
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new ServerFetchError(
      `Parse import failed: ${response.status} ${text}`.trim(),
      response,
    );
  }
  return (await response.json()) as ParseRouteLanesExcelResponse;
};

export const findAllUniqueShopNamesAndCodesFromTrucksAction = cache(async () => {
  const endpoint = `${BASE_API_URL}/truck/findAllUniqueShopNamesAndCodesFromTrucks`;
  
  return serverFetchJson<Array<{ name: string; code: string }>>(endpoint, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const findAllUniqueRemarksFromTrucksAction = cache(async () => {
  const endpoint = `${BASE_API_URL}/truck/findAllUniqueRemarksFromTrucks`;
  
  return serverFetchJson<string[]>(endpoint, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const findAllUniqueShopCodesFromTrucksAction = cache(async () => {
  const endpoint = `${BASE_API_URL}/truck/findAllUniqueShopCodesFromTrucks`;
  
  return serverFetchJson<string[]>(endpoint, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const findAllUniqueShopNamesFromTrucksAction = cache(async () => {
  const endpoint = `${BASE_API_URL}/truck/findAllUniqueShopNamesFromTrucks`;
  
  return serverFetchJson<string[]>(endpoint, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

// ---- Truck lane version snapshot (DB snapshot) ----

export interface CreateTruckLaneSnapshotRequest {
  truckLanceCode?: string | null;
  note?: string | null;
}

export interface TruckLaneVersionResponse {
  id: number;
  truckLanceCode: string;
  note: string | null;
  created: string | null;
  createdBy?: string | null;
  /** truck_lane_version.modifiedBy（BaseEntity） */
  modifiedBy?: string | null;
}

export interface TruckLaneVersionLineResponse {
  truckRowId: number;
  truckLanceCode: string | null;
  shopCode: string | null;
  branchName: string | null;
  districtReference: string | null;
  loadingSequence: number | null;
  departureTime: string | null;
  storeId: string;
  remark: string | null;
  logisticId: number | null;
}

export type DiffFieldChange = {
  field: string;
  from: string | null;
  to: string | null;
};

export type TruckLaneVersionDiffLine = {
  truckRowId: number;
  shopCode: string | null;
  changes: DiffFieldChange[];
  /** 快照車線（僅欄位異動時 changes 不含 truckLanceCode/remark） */
  truckLanceCode?: string | null;
  remark?: string | null;
};

export type LogisticMasterDiffLine = {
  logisticId: number;
  type: string;
  logisticName: string;
  carPlate: string;
  changeText: string;
};

export type TruckLaneVersionDiffResponse = {
  fromVersionId: number;
  toVersionId: number;
  changed: TruckLaneVersionDiffLine[];
  logisticMasterChanges?: LogisticMasterDiffLine[];
};

export const createTruckLaneSnapshotAction = async (data: CreateTruckLaneSnapshotRequest) => {
  const endpoint = `${BASE_API_URL}/truckLaneVersion/snapshot`;
  return serverFetchJson<TruckLaneVersionResponse>(endpoint, {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
};

export const listTruckLaneVersionsAction = cache(async (truckLanceCode?: string | null) => {
  const endpoint = `${BASE_API_URL}/truckLaneVersion`;
  const url =
    truckLanceCode != null && String(truckLanceCode).trim() !== ""
      ? `${endpoint}?truckLanceCode=${encodeURIComponent(String(truckLanceCode))}`
      : endpoint;
  return serverFetchJson<TruckLaneVersionResponse[]>(url, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const getTruckLaneVersionLinesAction = cache(async (versionId: number) => {
  const endpoint = `${BASE_API_URL}/truckLaneVersion/${versionId}/lines`;
  return serverFetchJson<TruckLaneVersionLineResponse[]>(endpoint, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
});

export const diffTruckLaneVersionsAction = async (
  fromVersionId: number,
  toVersionId: number,
) => {
  const endpoint = `${BASE_API_URL}/truckLaneVersion/diff`;
  const url = `${endpoint}?fromVersionId=${encodeURIComponent(String(fromVersionId))}&toVersionId=${encodeURIComponent(String(toVersionId))}`;
  return serverFetchJson<TruckLaneVersionDiffResponse>(url, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
};

export const restoreTruckLaneVersionAction = async (versionId: number) => {
  const endpoint = `${BASE_API_URL}/truckLaneVersion/${versionId}/restore`;
  return serverFetchJson<MessageResponse>(endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
  });
};

export type UpdateTruckLaneVersionNoteRequest = {
  note: string | null;
};

export const updateTruckLaneVersionNoteAction = async (
  versionId: number,
  data: UpdateTruckLaneVersionNoteRequest,
) => {
  const endpoint = `${BASE_API_URL}/truckLaneVersion/${versionId}/note`;
  return serverFetchJson<TruckLaneVersionResponse>(endpoint, {
    method: "PATCH",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
};

// ---- Truck lane schedule (server-side apply) ----

export type TruckLaneScheduleLineAction =
  | "MOVE"
  | "CREATE"
  | "DELETE"
  | "ENSURE_LANE";

export type TruckLaneMoveTargetRequest = {
  truckRowId: number;
  toTruckLanceCode: string;
  toRemark?: string | null;
  toStoreId: string;
  toLoadingSequence: number;
  toDistrictReference?: string | null;
};

export type TruckLaneScheduleLineRequest = {
  action: TruckLaneScheduleLineAction;
  truckRowId?: number | null;
  toTruckLanceCode: string;
  toRemark?: string | null;
  toStoreId: string;
  toLoadingSequence?: number | null;
  toDistrictReference?: string | null;
  shopId?: number | null;
  shopCode?: string | null;
  shopName?: string | null;
  departureTime?: string | null;
  logisticId?: number | null;
};

export type CreateTruckLaneScheduleRequest = {
  executeAt: string;
  note?: string | null;
  lines?: TruckLaneScheduleLineRequest[] | null;
  moves?: TruckLaneMoveTargetRequest[] | null;
};

export type TruckLaneScheduleLineResponse = {
  id: number;
  action: TruckLaneScheduleLineAction;
  truckRowId: number | null;
  shopCode: string | null;
  shopName: string | null;
  fromTruckLanceCode: string | null;
  fromRemark: string | null;
  fromStoreId: string | null;
  fromLoadingSequence?: number | null;
  fromDistrictReference?: string | null;
  fromDepartureTime?: string | null;
  toTruckLanceCode: string;
  toRemark: string | null;
  toStoreId: string;
  toDistrictReference?: string | null;
  toLoadingSequence?: number | null;
  departureTime?: string | null;
  lineStatus: string;
  errorMessage: string | null;
  appliedAt: string | null;
};

export type RouteExcelSchedulePlanPreviewRow = {
  action: TruckLaneScheduleLineAction;
  truckRowId: number | null;
  shopCode: string | null;
  shopName: string | null;
  toTruckLanceCode: string;
  toRemark: string | null;
  toStoreId: string;
  toLoadingSequence: number | null;
};

export type RouteExcelSchedulePlanError = {
  shopCode: string;
  shopName: string;
  message: string;
};

export type RouteExcelSchedulePlanCounts = {
  moves: number;
  creates: number;
  deletes: number;
  ensureLanes: number;
};

export type RouteExcelSchedulePlanResponse = {
  sheetCount: number;
  rowCount: number;
  lines: TruckLaneScheduleLineRequest[];
  previews: RouteExcelSchedulePlanPreviewRow[];
  errors: RouteExcelSchedulePlanError[];
  counts: RouteExcelSchedulePlanCounts;
};

export type TruckLaneScheduleResponse = {
  id: number;
  executeAt: string;
  status: string;
  source: string;
  note: string | null;
  appliedAt: string | null;
  errorMessage: string | null;
  snapshotVersionId: number | null;
  preApplySnapshotVersionId?: number | null;
  created: string | null;
  createdBy?: string | null;
  modifiedBy: string | null;
  lines?: TruckLaneScheduleLineResponse[] | null;
  lineCounts?: {
    total: number;
    applied: number;
    failed: number;
    pending: number;
  } | null;
};

export type PendingTruckRowIdsResponse = {
  truckRowIds: number[];
};

export type TruckLaneScheduleExcelPreviewRow = {
  rowIndex: number;
  shopCode: string;
  toTruckLanceCode: string;
  toRemark: string | null;
  toStoreId: string;
  executeAt: string | null;
  truckRowId: number | null;
};

export type TruckLaneScheduleExcelRowError = {
  rowIndex: number;
  message: string;
};

export type ParseTruckLaneScheduleExcelResponse = {
  rowCount: number;
  validCount: number;
  errorCount: number;
  rows: TruckLaneScheduleExcelPreviewRow[];
  errors: TruckLaneScheduleExcelRowError[];
};

export const createTruckLaneScheduleAction = async (
  data: CreateTruckLaneScheduleRequest,
): Promise<TruckLaneScheduleResponse> => {
  const endpoint = `${BASE_API_URL}/truckLaneSchedule`;
  return serverFetchJson<TruckLaneScheduleResponse>(endpoint, {
    method: "POST",
    body: JSON.stringify(data),
    headers: { "Content-Type": "application/json" },
  });
};

export const planTruckLaneScheduleFromRouteExcelAction = async (
  formData: FormData,
): Promise<RouteExcelSchedulePlanResponse> => {
  const response = await serverFetch(
    `${BASE_API_URL}/truckLaneSchedule/planFromRouteExcel`,
    {
      method: "POST",
      body: formData,
    },
  );
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new ServerFetchError(
      `Plan import failed: ${response.status} ${text}`.trim(),
      response,
    );
  }
  return (await response.json()) as RouteExcelSchedulePlanResponse;
};

export const listTruckLaneSchedulesAction = async (
  status?: string[],
  limit: number = 200,
): Promise<TruckLaneScheduleResponse[]> => {
  const params = new URLSearchParams();
  for (const s of status ?? []) {
    params.append("status", s);
  }
  params.set("limit", String(limit));
  const qs = params.toString();
  const base = `${BASE_API_URL}/truckLaneSchedule`;
  return serverFetchJson<TruckLaneScheduleResponse[]>(
    `${base}${qs ? `?${qs}` : ""}`,
    {
      method: "GET",
      headers: { "Content-Type": "application/json" },
    },
  );
};

export const getTruckLaneScheduleAction = async (
  id: number,
): Promise<TruckLaneScheduleResponse> => {
  const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}`;
  return serverFetchJson<TruckLaneScheduleResponse>(endpoint, {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  });
};

export const pendingTruckLaneScheduleShopIdsAction =
  async (): Promise<PendingTruckRowIdsResponse> => {
    const endpoint = `${BASE_API_URL}/truckLaneSchedule/pendingShopIds`;
    return serverFetchJson<PendingTruckRowIdsResponse>(endpoint, {
      method: "GET",
      headers: { "Content-Type": "application/json" },
    });
  };

export const cancelTruckLaneScheduleAction = async (
  id: number,
): Promise<TruckLaneScheduleResponse> => {
  const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}/cancel`;
  return serverFetchJson<TruckLaneScheduleResponse>(endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
  });
};

export const applyNowTruckLaneScheduleAction = async (
  id: number,
): Promise<TruckLaneScheduleResponse> => {
  const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}/applyNow`;
  return serverFetchJson<TruckLaneScheduleResponse>(endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
  });
};

export type RetryFailedTruckLaneScheduleRequest = {
  executeAt?: string | null;
};

export const retryFailedTruckLaneScheduleAction = async (
  id: number,
  body?: RetryFailedTruckLaneScheduleRequest,
): Promise<TruckLaneScheduleResponse> => {
  const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}/retry-failed`;
  return serverFetchJson<TruckLaneScheduleResponse>(endpoint, {
    method: "POST",
    body: JSON.stringify(body ?? {}),
    headers: { "Content-Type": "application/json" },
  });
};

export const ignoreTruckLaneScheduleAction = async (
  id: number,
): Promise<TruckLaneScheduleResponse> => {
  const endpoint = `${BASE_API_URL}/truckLaneSchedule/${id}/ignore`;
  return serverFetchJson<TruckLaneScheduleResponse>(endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
  });
};

export const parseTruckLaneScheduleExcelAction = async (
  formData: FormData,
  defaultExecuteAt?: string | null,
): Promise<ParseTruckLaneScheduleExcelResponse> => {
  const qs =
    defaultExecuteAt != null && defaultExecuteAt !== ""
      ? `?defaultExecuteAt=${encodeURIComponent(defaultExecuteAt)}`
      : "";
  const response = await serverFetch(
    `${BASE_API_URL}/truckLaneSchedule/parseExcel${qs}`,
    { method: "POST", body: formData },
  );
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new ServerFetchError(
      `Parse schedule Excel failed: ${response.status} ${text}`.trim(),
      response,
    );
  }
  return (await response.json()) as ParseTruckLaneScheduleExcelResponse;
};

export const importTruckLaneScheduleExcelAction = async (
  formData: FormData,
  defaultExecuteAt?: string | null,
  note?: string | null,
): Promise<TruckLaneScheduleResponse[]> => {
  const params = new URLSearchParams();
  if (defaultExecuteAt) params.set("defaultExecuteAt", defaultExecuteAt);
  if (note) params.set("note", note);
  const qs = params.toString() ? `?${params.toString()}` : "";
  const response = await serverFetch(
    `${BASE_API_URL}/truckLaneSchedule/importExcel${qs}`,
    { method: "POST", body: formData },
  );
  if (!response.ok) {
    const text = await response.text().catch(() => "");
    throw new ServerFetchError(
      `Import schedule Excel failed: ${response.status} ${text}`.trim(),
      response,
    );
  }
  return (await response.json()) as TruckLaneScheduleResponse[];
};