import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
import { NEXT_PUBLIC_API_URL } from "@/config/api";

const BASE = `${NEXT_PUBLIC_API_URL}/chart`;

function buildParams(params: Record<string, string | number | undefined>) {
  const p = new URLSearchParams();
  Object.entries(params).forEach(([k, v]) => {
    if (v !== undefined && v !== "") p.set(k, String(v));
  });
  return p.toString();
}

export interface StockTransactionsByDateRow {
  date: string;
  inQty: number;
  outQty: number;
  totalQty: number;
}

export interface DeliveryOrderByDateRow {
  date: string;
  orderCount: number;
  totalQty: number;
}

export interface PurchaseOrderByStatusRow {
  status: string;
  count: number;
}

/** Multi-select filters for purchase charts (repeated `supplierId` / `itemCode` / `purchaseOrderNo` query params). */
export type PurchaseOrderChartFilters = {
  supplierIds?: number[];
  itemCodes?: string[];
  purchaseOrderNos?: string[];
  /** Single supplier code (drill when row has no supplier id); not used with `supplierIds`. */
  supplierCode?: string;
};

function appendPurchaseOrderListParams(p: URLSearchParams, filters?: PurchaseOrderChartFilters) {
  (filters?.supplierIds ?? []).forEach((id) => {
    if (Number.isFinite(id) && id > 0) p.append("supplierId", String(id));
  });
  (filters?.itemCodes ?? []).forEach((c) => {
    const t = String(c).trim();
    if (t) p.append("itemCode", t);
  });
  (filters?.purchaseOrderNos ?? []).forEach((n) => {
    const t = String(n).trim();
    if (t) p.append("purchaseOrderNo", t);
  });
  const sc = filters?.supplierCode?.trim();
  if (sc) p.set("supplierCode", sc);
}

export interface PoFilterSupplierOption {
  supplierId: number;
  code: string;
  name: string;
}

export interface PoFilterItemOption {
  itemCode: string;
  itemName: string;
}

export interface PoFilterPoNoOption {
  poNo: string;
}

export interface PurchaseOrderFilterOptions {
  suppliers: PoFilterSupplierOption[];
  items: PoFilterItemOption[];
  poNos: PoFilterPoNoOption[];
}

export interface PurchaseOrderEstimatedArrivalRow {
  bucket: string;
  count: number;
}

export interface PurchaseOrderDetailByStatusRow {
  purchaseOrderId: number;
  purchaseOrderNo: string;
  status: string;
  orderDate: string;
  estimatedArrivalDate: string;
  /** Shop / supplier FK; use for grouping when code is blank */
  supplierId: number | null;
  supplierCode: string;
  supplierName: string;
  itemCount: number;
  totalQty: number;
}

export interface PurchaseOrderItemRow {
  purchaseOrderLineId: number;
  itemCode: string;
  itemName: string;
  orderedQty: number;
  uom: string;
  receivedQty: number;
  pendingQty: number;
}

export interface StockInOutByDateRow {
  date: string;
  inQty: number;
  outQty: number;
}

export interface TopDeliveryItemsRow {
  itemCode: string;
  itemName: string;
  totalQty: number;
}

export interface StockBalanceTrendRow {
  date: string;
  balance: number;
}

export interface ConsumptionTrendByMonthRow {
  month: string;
  outQty: number;
}

export interface StaffDeliveryPerformanceRow {
  date: string;
  staffName: string;
  orderCount: number;
  totalMinutes: number;
}

export interface StaffOption {
  staffNo: string;
  name: string;
}

export async function fetchStaffDeliveryPerformanceHandlers(): Promise<StaffOption[]> {
  const res = await clientAuthFetch(`${BASE}/staff-delivery-performance-handlers`);
  if (!res.ok) throw new Error("Failed to fetch staff list");
  const data = await res.json();
  if (!Array.isArray(data)) return [];
  return (data as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    staffNo: String(r.staffNo ?? ""),
    name: String(r.name ?? ""),
  }));
}

// Job order
export interface JobOrderByStatusRow {
  status: string;
  count: number;
}

export interface JobOrderCountByDateRow {
  date: string;
  orderCount: number;
}

export interface JobOrderCreatedCompletedRow {
  date: string;
  createdCount: number;
  completedCount: number;
}

export interface ProductionScheduleByDateRow {
  date: string;
  scheduledItemCount: number;
  totalEstProdCount: number;
}

export interface PlannedDailyOutputRow {
  itemCode: string;
  itemName: string;
  dailyQty: number;
}

export async function fetchJobOrderByStatus(
  targetDate?: string
): Promise<JobOrderByStatusRow[]> {
  const q = targetDate ? buildParams({ targetDate }) : "";
  const res = await clientAuthFetch(
    q ? `${BASE}/job-order-by-status?${q}` : `${BASE}/job-order-by-status`
  );
  if (!res.ok) throw new Error("Failed to fetch job order by status");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    status: String(r.status ?? ""),
    count: Number(r.count ?? 0),
  }));
}

export async function fetchJobOrderCountByDate(
  startDate?: string,
  endDate?: string
): Promise<JobOrderCountByDateRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(`${BASE}/job-order-count-by-date?${q}`);
  if (!res.ok) throw new Error("Failed to fetch job order count by date");
  const data = await res.json();
  return normalizeChartRows(data, "date", ["orderCount"]);
}

export async function fetchJobOrderCreatedCompletedByDate(
  startDate?: string,
  endDate?: string
): Promise<JobOrderCreatedCompletedRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(
    `${BASE}/job-order-created-completed-by-date?${q}`
  );
  if (!res.ok) throw new Error("Failed to fetch job order created/completed");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    date: String(r.date ?? ""),
    createdCount: Number(r.createdCount ?? 0),
    completedCount: Number(r.completedCount ?? 0),
  }));
}

export interface JobMaterialPendingPickedRow {
  date: string;
  pendingCount: number;
  pickedCount: number;
}

export async function fetchJobMaterialPendingPickedByDate(
  startDate?: string,
  endDate?: string
): Promise<JobMaterialPendingPickedRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(`${BASE}/job-material-pending-picked-by-date?${q}`);
  if (!res.ok) throw new Error("Failed to fetch job material pending/picked");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    date: String(r.date ?? ""),
    pendingCount: Number(r.pendingCount ?? 0),
    pickedCount: Number(r.pickedCount ?? 0),
  }));
}

export interface JobProcessPendingCompletedRow {
  date: string;
  pendingCount: number;
  completedCount: number;
}

export async function fetchJobProcessPendingCompletedByDate(
  startDate?: string,
  endDate?: string
): Promise<JobProcessPendingCompletedRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(`${BASE}/job-process-pending-completed-by-date?${q}`);
  if (!res.ok) throw new Error("Failed to fetch job process pending/completed");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    date: String(r.date ?? ""),
    pendingCount: Number(r.pendingCount ?? 0),
    completedCount: Number(r.completedCount ?? 0),
  }));
}

export interface JobEquipmentWorkingWorkedRow {
  date: string;
  workingCount: number;
  workedCount: number;
}

export async function fetchJobEquipmentWorkingWorkedByDate(
  startDate?: string,
  endDate?: string
): Promise<JobEquipmentWorkingWorkedRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(`${BASE}/job-equipment-working-worked-by-date?${q}`);
  if (!res.ok) throw new Error("Failed to fetch job equipment working/worked");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    date: String(r.date ?? ""),
    workingCount: Number(r.workingCount ?? 0),
    workedCount: Number(r.workedCount ?? 0),
  }));
}

export interface JobOrderBoardRow {
  jobOrderId: number;
  code: string;
  status: string;
  planStart: string;
  actualStart: string;
  planEnd: string;
  actualEnd: string;
  materialPendingCount: number;
  materialPickedCount: number;
  processTotalCount: number;
  processCompletedCount: number;
  currentProcessCode: string;
  currentProcessName: string;
  currentProcessStartTime: string;
  /** FG/WIP job stock-in: sum acceptedQty on all linked lines */
  stockInAcceptedQtyTotal: number;
  /** Lines QC-passed, waiting putaway (receiving / received) */
  fgReadyToStockInCount: number;
  fgReadyToStockInQty: number;
  fgInQcLineCount: number;
  fgInQcQty: number;
  fgStockedQty: number;
  /** Same sources as /jo/edit 工藝流程 summary (product process + lines) */
  itemCode: string;
  itemName: string;
  jobTypeName: string;
  reqQty: number;
  outputQtyUom: string;
  productionDate: string;
  /** Sum of line processingTime (matches ProcessSummaryHeader 預計所需時間) */
  planProcessingMinsTotal: number;
  /** Sum of setup + changeover minutes on all lines */
  planSetupChangeoverMinsTotal: number;
  productProcessStart: string;
  /** Σ line durations in decimal minutes (seconds÷60); sub-minute shown; Pass w/o endTime uses planned processing min */
  actualLineMinsTotal: number;
}

function numField(v: unknown): number {
  if (v == null || v === "") return 0;
  const n = Number(v);
  return Number.isFinite(n) ? n : 0;
}

function mapJobOrderBoardRow(r: Record<string, unknown>): JobOrderBoardRow {
  const id = r.jobOrderId ?? r.joborderid;
  return {
    jobOrderId: Number(id ?? 0),
    code: String(r.code ?? ""),
    status: String(r.status ?? ""),
    planStart: String(r.planStart ?? r.planstart ?? ""),
    actualStart: String(r.actualStart ?? r.actualstart ?? ""),
    planEnd: String(r.planEnd ?? r.planend ?? ""),
    actualEnd: String(r.actualEnd ?? r.actualend ?? ""),
    materialPendingCount: Number(r.materialPendingCount ?? r.materialpendingcount ?? 0),
    materialPickedCount: Number(r.materialPickedCount ?? r.materialpickedcount ?? 0),
    processTotalCount: Number(r.processTotalCount ?? r.processtotalcount ?? 0),
    processCompletedCount: Number(r.processCompletedCount ?? r.processcompletedcount ?? 0),
    currentProcessCode: String(r.currentProcessCode ?? r.currentprocesscode ?? ""),
    currentProcessName: String(r.currentProcessName ?? r.currentprocessname ?? ""),
    currentProcessStartTime: String(r.currentProcessStartTime ?? r.currentprocessstarttime ?? ""),
    stockInAcceptedQtyTotal: Number(r.stockInAcceptedQtyTotal ?? r.stockinacceptedqtytotal ?? 0),
    fgReadyToStockInCount: Number(r.fgReadyToStockInCount ?? r.fgreadytostockincount ?? 0),
    fgReadyToStockInQty: Number(r.fgReadyToStockInQty ?? r.fgreadytostockinqty ?? 0),
    fgInQcLineCount: Number(r.fgInQcLineCount ?? r.fginqclinecount ?? 0),
    fgInQcQty: Number(r.fgInQcQty ?? r.fginqcqty ?? 0),
    fgStockedQty: Number(r.fgStockedQty ?? r.fgstockedqty ?? 0),
    itemCode: String(r.itemCode ?? r.itemcode ?? ""),
    itemName: String(r.itemName ?? r.itemname ?? ""),
    jobTypeName: String(r.jobTypeName ?? r.jobtypename ?? ""),
    reqQty: numField(r.reqQty ?? r.reqqty),
    outputQtyUom: String(r.outputQtyUom ?? r.outputqtyuom ?? ""),
    productionDate: String(r.productionDate ?? r.productiondate ?? ""),
    planProcessingMinsTotal: numField(r.planProcessingMinsTotal ?? r.planprocessingminstotal),
    planSetupChangeoverMinsTotal: numField(r.planSetupChangeoverMinsTotal ?? r.plansetupchangeoverminstotal),
    productProcessStart: String(r.productProcessStart ?? r.productprocessstart ?? ""),
    actualLineMinsTotal: numField(r.actualLineMinsTotal ?? r.actuallineminstotal),
  };
}

/** Per-job board rows. With [incompleteOnly], excludes status completed (backend LOWER(status) <> 'completed'). */
export async function fetchJobOrderBoard(
  targetDate?: string,
  opts?: { incompleteOnly?: boolean },
): Promise<JobOrderBoardRow[]> {
  const params: Record<string, string | number | undefined> = {};
  if (targetDate) params.targetDate = targetDate;
  if (opts?.incompleteOnly) params.incompleteOnly = "true";
  const q = buildParams(params);
  const res = await clientAuthFetch(q ? `${BASE}/job-order-board?${q}` : `${BASE}/job-order-board`);
  if (!res.ok) throw new Error("Failed to fetch job order board");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map(mapJobOrderBoardRow);
}

export interface ProcessBoardRow {
  jopId: number;
  jobOrderId: number;
  jobOrderCode: string;
  jobOrderStatus: string;
  processId: number;
  processCode: string;
  processName: string;
  seqNo: number;
  rowStatus: string;
  jobPlanStart: string;
  startTime: string;
  endTime: string;
  /** Derived: pending | in_progress | completed */
  boardStatus: string;
  /** 工藝流程步驟名稱（productprocessline.name；多筆以 | 分隔）；無明細時為主檔工序名。 */
  lineStepName: string;
  /** 描述 */
  lineDescription: string;
  /** 設備類型-設備名稱-編號（與工單工藝流程一致） */
  lineEquipmentLabel: string;
  /** 操作員／員工顯示名 */
  lineOperatorInfo: string;
  itemCode: string;
  itemName: string;
  jobTypeName: string;
  reqQty: number;
  outputQtyUom: string;
  productionDate: string;
  planProcessingMinsTotal: number;
  planSetupChangeoverMinsTotal: number;
  productProcessStart: string;
  actualLineMinsTotal: number;
  /** This BOM step: sum(processing+setup+changeover) on matching lines */
  stepPlanMins: number;
  /** This BOM step: Σ line durations in decimal minutes (seconds÷60); Pass/Completed without endTime uses planned processing min as fallback */
  stepActualMins: number;
}

function mapProcessBoardRow(r: Record<string, unknown>): ProcessBoardRow {
  return {
    jopId: Number(r.jopId ?? r.jopid ?? 0),
    jobOrderId: Number(r.jobOrderId ?? r.joborderid ?? 0),
    jobOrderCode: String(r.jobOrderCode ?? r.jobordercode ?? ""),
    jobOrderStatus: String(r.jobOrderStatus ?? r.joborderstatus ?? ""),
    processId: Number(r.processId ?? r.processid ?? 0),
    processCode: String(r.processCode ?? r.processcode ?? ""),
    processName: String(r.processName ?? r.processname ?? ""),
    seqNo: Number(r.seqNo ?? r.seqno ?? 0),
    rowStatus: String(r.rowStatus ?? r.rowstatus ?? ""),
    jobPlanStart: String(r.jobPlanStart ?? r.jobplanstart ?? ""),
    startTime: String(r.startTime ?? r.starttime ?? ""),
    endTime: String(r.endTime ?? r.endtime ?? ""),
    boardStatus: String(r.boardStatus ?? r.boardstatus ?? "pending").toLowerCase(),
    lineStepName: String(r.lineStepName ?? r.linestepname ?? r.line_step_name ?? ""),
    lineDescription: String(r.lineDescription ?? r.linedescription ?? r.line_description ?? ""),
    lineEquipmentLabel: String(r.lineEquipmentLabel ?? r.lineequipmentlabel ?? r.line_equipment_label ?? ""),
    lineOperatorInfo: String(r.lineOperatorInfo ?? r.lineoperatorinfo ?? r.line_operator_info ?? ""),
    itemCode: String(r.itemCode ?? r.itemcode ?? ""),
    itemName: String(r.itemName ?? r.itemname ?? ""),
    jobTypeName: String(r.jobTypeName ?? r.jobtypename ?? ""),
    reqQty: numField(r.reqQty ?? r.reqqty),
    outputQtyUom: String(r.outputQtyUom ?? r.outputqtyuom ?? ""),
    productionDate: String(r.productionDate ?? r.productiondate ?? ""),
    planProcessingMinsTotal: numField(r.planProcessingMinsTotal ?? r.planprocessingminstotal),
    planSetupChangeoverMinsTotal: numField(r.planSetupChangeoverMinsTotal ?? r.plansetupchangeoverminstotal),
    productProcessStart: String(r.productProcessStart ?? r.productprocessstart ?? ""),
    actualLineMinsTotal: numField(r.actualLineMinsTotal ?? r.actuallineminstotal),
    stepPlanMins: numField(r.stepPlanMins ?? r.stepplanmins),
    stepActualMins: numField(r.stepActualMins ?? r.stepactualmins),
  };
}

/** Per job_order_process line; same filters as job-order board. */
export async function fetchProcessBoard(
  targetDate?: string,
  opts?: { incompleteOnly?: boolean },
): Promise<ProcessBoardRow[]> {
  const params: Record<string, string | number | undefined> = {};
  if (targetDate) params.targetDate = targetDate;
  if (opts?.incompleteOnly) params.incompleteOnly = "true";
  const q = buildParams(params);
  const res = await clientAuthFetch(q ? `${BASE}/process-board?${q}` : `${BASE}/process-board`);
  if (!res.ok) throw new Error("Failed to fetch process board");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map(mapProcessBoardRow);
}

export interface EquipmentUsageBoardRow {
  jopdId: number;
  equipmentId: number;
  equipmentCode: string;
  equipmentName: string;
  jobOrderId: number;
  jobOrderCode: string;
  jobPlanStart: string;
  processCode: string;
  processName: string;
  operatingStart: string;
  operatingEnd: string;
  /** Estimated usage minutes (start–end diff, or 產線 processingTime when Pass/Completed without end). */
  usageMinutes: number;
  workingNow: number;
  operatorUsername: string;
  operatorName: string;
}

function mapEquipmentUsageBoardRow(r: Record<string, unknown>): EquipmentUsageBoardRow {
  return {
    jopdId: Number(r.jopdId ?? r.jopdid ?? 0),
    equipmentId: Number(r.equipmentId ?? r.equipmentid ?? 0),
    equipmentCode: String(r.equipmentCode ?? r.equipmentcode ?? ""),
    equipmentName: String(r.equipmentName ?? r.equipmentname ?? ""),
    jobOrderId: Number(r.jobOrderId ?? r.joborderid ?? 0),
    jobOrderCode: String(r.jobOrderCode ?? r.jobordercode ?? ""),
    jobPlanStart: String(r.jobPlanStart ?? r.jobplanstart ?? ""),
    processCode: String(r.processCode ?? r.processcode ?? ""),
    processName: String(r.processName ?? r.processname ?? ""),
    operatingStart: String(r.operatingStart ?? r.operatingstart ?? ""),
    operatingEnd: String(r.operatingEnd ?? r.operatingend ?? ""),
    usageMinutes: Number(r.usageMinutes ?? r.usageminutes ?? 0),
    workingNow: Number(r.workingNow ?? r.workingnow ?? 0),
    operatorUsername: String(r.operatorUsername ?? r.operatorusername ?? ""),
    operatorName: String(r.operatorName ?? r.operatorname ?? ""),
  };
}

/** Day = COALESCE(line/jopd times, jop.endTime, planStart). Includes productprocessline (工藝流程) and job_order_process_detail. Omit targetDate = server today. */
export async function fetchEquipmentUsageBoard(targetDate?: string): Promise<EquipmentUsageBoardRow[]> {
  const q = buildParams({ targetDate: targetDate ?? "" });
  const res = await clientAuthFetch(q ? `${BASE}/equipment-usage-board?${q}` : `${BASE}/equipment-usage-board`);
  if (!res.ok) throw new Error("Failed to fetch equipment usage board");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map(mapEquipmentUsageBoardRow);
}

export async function fetchProductionScheduleByDate(
  startDate?: string,
  endDate?: string
): Promise<ProductionScheduleByDateRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(
    `${BASE}/production-schedule-by-date?${q}`
  );
  if (!res.ok) throw new Error("Failed to fetch production schedule by date");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    date: String(r.date ?? ""),
    scheduledItemCount: Number(r.scheduledItemCount ?? r.scheduleCount ?? 0),
    totalEstProdCount: Number(r.totalEstProdCount ?? 0),
  }));
}

export async function fetchPlannedDailyOutputByItem(
  limit = 20
): Promise<PlannedDailyOutputRow[]> {
  const res = await clientAuthFetch(
    `${BASE}/planned-daily-output-by-item?limit=${limit}`
  );
  if (!res.ok) throw new Error("Failed to fetch planned daily output");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    itemCode: String(r.itemCode ?? ""),
    itemName: String(r.itemName ?? ""),
    dailyQty: Number(r.dailyQty ?? 0),
  }));
}

/** Planned production by date and by item (production_schedule). */
export interface PlannedOutputByDateAndItemRow {
  date: string;
  itemCode: string;
  itemName: string;
  qty: number;
}

export async function fetchPlannedOutputByDateAndItem(
  startDate?: string,
  endDate?: string
): Promise<PlannedOutputByDateAndItemRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(
    q ? `${BASE}/planned-output-by-date-and-item?${q}` : `${BASE}/planned-output-by-date-and-item`
  );
  if (!res.ok) throw new Error("Failed to fetch planned output by date and item");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    date: String(r.date ?? ""),
    itemCode: String(r.itemCode ?? ""),
    itemName: String(r.itemName ?? ""),
    qty: Number(r.qty ?? 0),
  }));
}

/** Warehouse / lane filter for staff delivery performance chart (delivery_order_pick_order.store_id). */
export type StaffDeliveryPerformanceStoreFilter = "all" | "2/F" | "4/F" | "null_only";

export async function fetchStaffDeliveryPerformance(
  startDate?: string,
  endDate?: string,
  staffNos?: string[],
  storeFilter: StaffDeliveryPerformanceStoreFilter = "all"
): Promise<StaffDeliveryPerformanceRow[]> {
  const p = new URLSearchParams();
  if (startDate) p.set("startDate", startDate);
  if (endDate) p.set("endDate", endDate);
  (staffNos ?? []).forEach((no) => p.append("staffNo", no));
  if (storeFilter === "null_only") p.set("storeIdNull", "true");
  else if (storeFilter === "2/F") p.set("storeId", "2/F");
  else if (storeFilter === "4/F") p.set("storeId", "4/F");
  const q = p.toString();
  const res = await clientAuthFetch(
    q ? `${BASE}/staff-delivery-performance?${q}` : `${BASE}/staff-delivery-performance`
  );
  if (!res.ok) throw new Error("Failed to fetch staff delivery performance");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => {
    // Accept camelCase or lowercase keys (JDBC/DB may return different casing)
    const row = r as Record<string, unknown>;
    return {
      date: String(row.date ?? row.Date ?? ""),
      staffName: String(row.staffName ?? row.staffname ?? ""),
      orderCount: Number(row.orderCount ?? row.ordercount ?? 0),
      totalMinutes: Number(row.totalMinutes ?? row.totalminutes ?? 0),
    };
  });
}

export async function fetchStockTransactionsByDate(
  startDate?: string,
  endDate?: string
): Promise<StockTransactionsByDateRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(`${BASE}/stock-transactions-by-date?${q}`);
  if (!res.ok) throw new Error("Failed to fetch stock transactions by date");
  const data = await res.json();
  return normalizeChartRows(data, "date", ["inQty", "outQty", "totalQty"]);
}

export async function fetchDeliveryOrderByDate(
  startDate?: string,
  endDate?: string
): Promise<DeliveryOrderByDateRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(`${BASE}/delivery-order-by-date?${q}`);
  if (!res.ok) throw new Error("Failed to fetch delivery order by date");
  const data = await res.json();
  return normalizeChartRows(data, "date", ["orderCount", "totalQty"]);
}

export async function fetchPurchaseOrderByStatus(
  targetDate?: string,
  filters?: PurchaseOrderChartFilters
): Promise<PurchaseOrderByStatusRow[]> {
  const p = new URLSearchParams();
  if (targetDate) p.set("targetDate", targetDate);
  appendPurchaseOrderListParams(p, filters);
  const q = p.toString();
  const res = await clientAuthFetch(
    q ? `${BASE}/purchase-order-by-status?${q}` : `${BASE}/purchase-order-by-status`
  );
  if (!res.ok) throw new Error("Failed to fetch purchase order by status");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    status: String(r.status ?? ""),
    count: Number(r.count ?? 0),
  }));
}

export async function fetchPurchaseOrderFilterOptions(
  targetDate?: string
): Promise<PurchaseOrderFilterOptions> {
  const p = new URLSearchParams();
  if (targetDate) p.set("targetDate", targetDate);
  const q = p.toString();
  const res = await clientAuthFetch(
    q ? `${BASE}/purchase-order-filter-options?${q}` : `${BASE}/purchase-order-filter-options`
  );
  if (!res.ok) throw new Error("Failed to fetch purchase order filter options");
  const data = await res.json();
  const row = (data ?? {}) as Record<string, unknown>;
  const suppliers = (Array.isArray(row.suppliers) ? row.suppliers : []) as Record<string, unknown>[];
  const items = (Array.isArray(row.items) ? row.items : []) as Record<string, unknown>[];
  const poNos = (Array.isArray(row.poNos) ? row.poNos : []) as Record<string, unknown>[];
  return {
    suppliers: suppliers.map((r) => ({
      supplierId: Number(r.supplierId ?? r.supplierid ?? 0),
      code: String(r.code ?? ""),
      name: String(r.name ?? ""),
    })),
    items: items.map((r) => ({
      itemCode: String(r.itemCode ?? r.itemcode ?? ""),
      itemName: String(r.itemName ?? r.itemname ?? ""),
    })),
    poNos: poNos.map((r) => ({
      poNo: String(r.poNo ?? r.pono ?? ""),
    })),
  };
}

export async function fetchPurchaseOrderEstimatedArrivalSummary(
  targetDate?: string,
  filters?: PurchaseOrderChartFilters
): Promise<PurchaseOrderEstimatedArrivalRow[]> {
  const p = new URLSearchParams();
  if (targetDate) p.set("targetDate", targetDate);
  appendPurchaseOrderListParams(p, filters);
  const q = p.toString();
  const res = await clientAuthFetch(
    q
      ? `${BASE}/purchase-order-estimated-arrival-summary?${q}`
      : `${BASE}/purchase-order-estimated-arrival-summary`
  );
  if (!res.ok) throw new Error("Failed to fetch estimated arrival summary");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    bucket: String(r.bucket ?? ""),
    count: Number(r.count ?? 0),
  }));
}

export interface EstimatedArrivalBreakdownSupplierRow {
  supplierId: number | null;
  supplierCode: string;
  supplierName: string;
  poCount: number;
}

export interface EstimatedArrivalBreakdownItemRow {
  itemCode: string;
  itemName: string;
  poCount: number;
  totalQty: number;
}

export interface EstimatedArrivalBreakdownPoRow {
  purchaseOrderId: number;
  purchaseOrderNo: string;
  status: string;
  orderDate: string;
  supplierId: number | null;
  supplierCode: string;
  supplierName: string;
}

export interface PurchaseOrderEstimatedArrivalBreakdown {
  suppliers: EstimatedArrivalBreakdownSupplierRow[];
  items: EstimatedArrivalBreakdownItemRow[];
  purchaseOrders: EstimatedArrivalBreakdownPoRow[];
}

/** Related suppliers / items / POs for one 預計送貨 bucket (same bar filters as the donut). */
export async function fetchPurchaseOrderEstimatedArrivalBreakdown(
  targetDate: string,
  estimatedArrivalBucket: string,
  filters?: PurchaseOrderChartFilters
): Promise<PurchaseOrderEstimatedArrivalBreakdown> {
  const p = new URLSearchParams();
  p.set("targetDate", targetDate);
  p.set("estimatedArrivalBucket", estimatedArrivalBucket.trim().toLowerCase());
  appendPurchaseOrderListParams(p, filters);
  const res = await clientAuthFetch(`${BASE}/purchase-order-estimated-arrival-breakdown?${p.toString()}`);
  if (!res.ok) throw new Error("Failed to fetch estimated arrival breakdown");
  const data = await res.json();
  const row = (data ?? {}) as Record<string, unknown>;
  const suppliers = (Array.isArray(row.suppliers) ? row.suppliers : []) as Record<string, unknown>[];
  const items = (Array.isArray(row.items) ? row.items : []) as Record<string, unknown>[];
  const purchaseOrders = (Array.isArray(row.purchaseOrders) ? row.purchaseOrders : []) as Record<string, unknown>[];
  return {
    suppliers: suppliers.map((r) => ({
      supplierId: (() => {
        const v = r.supplierId ?? r.supplierid;
        if (v == null || v === "") return null;
        const n = Number(v);
        return Number.isFinite(n) ? n : null;
      })(),
      supplierCode: String(r.supplierCode ?? r.suppliercode ?? ""),
      supplierName: String(r.supplierName ?? r.suppliername ?? ""),
      poCount: Number(r.poCount ?? r.pocount ?? 0),
    })),
    items: items.map((r) => ({
      itemCode: String(r.itemCode ?? r.itemcode ?? ""),
      itemName: String(r.itemName ?? r.itemname ?? ""),
      poCount: Number(r.poCount ?? r.pocount ?? 0),
      totalQty: Number(r.totalQty ?? r.totalqty ?? 0),
    })),
    purchaseOrders: purchaseOrders.map((r) => ({
      purchaseOrderId: Number(r.purchaseOrderId ?? r.purchaseorderid ?? 0),
      purchaseOrderNo: String(r.purchaseOrderNo ?? r.purchaseorderno ?? ""),
      status: String(r.status ?? ""),
      orderDate: String(r.orderDate ?? r.orderdate ?? ""),
      supplierId: (() => {
        const v = r.supplierId ?? r.supplierid;
        if (v == null || v === "") return null;
        const n = Number(v);
        return Number.isFinite(n) ? n : null;
      })(),
      supplierCode: String(r.supplierCode ?? r.suppliercode ?? ""),
      supplierName: String(r.supplierName ?? r.suppliername ?? ""),
    })),
  };
}

export type PurchaseOrderDrillQuery = PurchaseOrderChartFilters & {
  /** order = PO order date; complete = PO complete date (for received/completed on a day) */
  dateFilter?: "order" | "complete";
  /** delivered | not_delivered | cancelled | other — same as 預計送貨 donut buckets */
  estimatedArrivalBucket?: string;
};

export async function fetchPurchaseOrderDetailsByStatus(
  status: string,
  targetDate?: string,
  opts?: PurchaseOrderDrillQuery
): Promise<PurchaseOrderDetailByStatusRow[]> {
  const p = new URLSearchParams();
  p.set("status", status.trim().toLowerCase());
  if (targetDate) p.set("targetDate", targetDate);
  if (opts?.dateFilter) p.set("dateFilter", opts.dateFilter);
  if (opts?.estimatedArrivalBucket?.trim()) {
    p.set("estimatedArrivalBucket", opts.estimatedArrivalBucket.trim().toLowerCase());
  }
  appendPurchaseOrderListParams(p, opts);
  const q = p.toString();
  const res = await clientAuthFetch(`${BASE}/purchase-order-details-by-status?${q}`);
  if (!res.ok) throw new Error("Failed to fetch purchase order details by status");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    purchaseOrderId: Number(r.purchaseOrderId ?? 0),
    purchaseOrderNo: String(r.purchaseOrderNo ?? ""),
    status: String(r.status ?? ""),
    orderDate: String(r.orderDate ?? ""),
    estimatedArrivalDate: String(r.estimatedArrivalDate ?? ""),
    supplierId: (() => {
      const v = r.supplierId;
      if (v == null || v === "") return null;
      const n = Number(v);
      return Number.isFinite(n) && n > 0 ? n : null;
    })(),
    supplierCode: String(r.supplierCode ?? ""),
    supplierName: String(r.supplierName ?? ""),
    itemCount: Number(r.itemCount ?? 0),
    totalQty: Number(r.totalQty ?? 0),
  }));
}

export async function fetchPurchaseOrderItems(
  purchaseOrderId: number
): Promise<PurchaseOrderItemRow[]> {
  const q = buildParams({ purchaseOrderId });
  const res = await clientAuthFetch(`${BASE}/purchase-order-items?${q}`);
  if (!res.ok) throw new Error("Failed to fetch purchase order items");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    purchaseOrderLineId: Number(r.purchaseOrderLineId ?? 0),
    itemCode: String(r.itemCode ?? ""),
    itemName: String(r.itemName ?? ""),
    orderedQty: Number(r.orderedQty ?? 0),
    uom: String(r.uom ?? ""),
    receivedQty: Number(r.receivedQty ?? 0),
    pendingQty: Number(r.pendingQty ?? 0),
  }));
}

export async function fetchPurchaseOrderItemsByStatus(
  status: string,
  targetDate?: string,
  opts?: PurchaseOrderDrillQuery
): Promise<PurchaseOrderItemRow[]> {
  const p = new URLSearchParams();
  p.set("status", status.trim().toLowerCase());
  if (targetDate) p.set("targetDate", targetDate);
  if (opts?.dateFilter) p.set("dateFilter", opts.dateFilter);
  if (opts?.estimatedArrivalBucket?.trim()) {
    p.set("estimatedArrivalBucket", opts.estimatedArrivalBucket.trim().toLowerCase());
  }
  appendPurchaseOrderListParams(p, opts);
  const q = p.toString();
  const res = await clientAuthFetch(`${BASE}/purchase-order-items-by-status?${q}`);
  if (!res.ok) throw new Error("Failed to fetch purchase order items by status");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    purchaseOrderLineId: 0,
    itemCode: String(r.itemCode ?? ""),
    itemName: String(r.itemName ?? ""),
    orderedQty: Number(r.orderedQty ?? 0),
    uom: String(r.uom ?? ""),
    receivedQty: Number(r.receivedQty ?? 0),
    pendingQty: Number(r.pendingQty ?? 0),
  }));
}

export async function fetchStockInOutByDate(
  startDate?: string,
  endDate?: string
): Promise<StockInOutByDateRow[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(`${BASE}/stock-in-out-by-date?${q}`);
  if (!res.ok) throw new Error("Failed to fetch stock in/out by date");
  const data = await res.json();
  return normalizeChartRows(data, "date", ["inQty", "outQty"]);
}

export interface TopDeliveryItemOption {
  itemCode: string;
  itemName: string;
}

export async function fetchTopDeliveryItemsItemOptions(
  startDate?: string,
  endDate?: string
): Promise<TopDeliveryItemOption[]> {
  const q = buildParams({ startDate: startDate ?? "", endDate: endDate ?? "" });
  const res = await clientAuthFetch(
    q ? `${BASE}/top-delivery-items-item-options?${q}` : `${BASE}/top-delivery-items-item-options`
  );
  if (!res.ok) throw new Error("Failed to fetch item options");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    itemCode: String(r.itemCode ?? ""),
    itemName: String(r.itemName ?? ""),
  }));
}

export async function fetchTopDeliveryItems(
  startDate?: string,
  endDate?: string,
  limit = 10,
  itemCodes?: string[]
): Promise<TopDeliveryItemsRow[]> {
  const p = new URLSearchParams();
  if (startDate) p.set("startDate", startDate);
  if (endDate) p.set("endDate", endDate);
  p.set("limit", String(limit));
  (itemCodes ?? []).forEach((code) => p.append("itemCode", code));
  const q = p.toString();
  const res = await clientAuthFetch(`${BASE}/top-delivery-items?${q}`);
  if (!res.ok) throw new Error("Failed to fetch top delivery items");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    itemCode: String(r.itemCode ?? ""),
    itemName: String(r.itemName ?? ""),
    totalQty: Number(r.totalQty ?? 0),
  }));
}

export async function fetchStockBalanceTrend(
  startDate?: string,
  endDate?: string,
  itemCode?: string
): Promise<StockBalanceTrendRow[]> {
  const q = buildParams({
    startDate: startDate ?? "",
    endDate: endDate ?? "",
    itemCode: itemCode ?? "",
  });
  const res = await clientAuthFetch(`${BASE}/stock-balance-trend?${q}`);
  if (!res.ok) throw new Error("Failed to fetch stock balance trend");
  const data = await res.json();
  return normalizeChartRows(data, "date", ["balance"]);
}

export async function fetchConsumptionTrendByMonth(
  year?: number,
  startDate?: string,
  endDate?: string,
  itemCode?: string
): Promise<ConsumptionTrendByMonthRow[]> {
  const q = buildParams({
    year: year ?? "",
    startDate: startDate ?? "",
    endDate: endDate ?? "",
    itemCode: itemCode ?? "",
  });
  const res = await clientAuthFetch(`${BASE}/consumption-trend-by-month?${q}`);
  if (!res.ok) throw new Error("Failed to fetch consumption trend");
  const data = await res.json();
  return ((Array.isArray(data) ? data : []) as Record<string, unknown>[]).map((r: Record<string, unknown>) => ({
    month: String(r.month ?? ""),
    outQty: Number(r.outQty ?? 0),
  }));
}

/** Normalize rows: ensure date key is string and numeric keys are numbers (backend may return BigDecimal/Long). */
function normalizeChartRows<T>(
  rows: unknown[],
  dateKey: string,
  numberKeys: string[]
): T[] {
  if (!Array.isArray(rows)) return [];
  return rows.map((r: unknown) => {
    const row = r as Record<string, unknown>;
    const out: Record<string, unknown> = {};
    out[dateKey] = row[dateKey] != null ? String(row[dateKey]) : "";
    numberKeys.forEach((k) => {
      out[k] = Number(row[k]) || 0;
    });
    return out as T;
  });
}
