import type { TruckLaneMoveTargetRequest } from "@/app/api/shop/actions";
import type { ScheduleLaneOption } from "@/components/Shop/ScheduleChangeModal";
import { isExecuteAtTooEarly } from "@/components/Shop/scheduleExecuteAt";

export type ScheduleMoveValidationError = {
  truckRowId: number;
  messageKey: string;
  messageParams?: Record<string, string | number>;
};

export type ValidateScheduleMovesInput = {
  moves: TruckLaneMoveTargetRequest[];
  lanes: ScheduleLaneOption[];
  /** truck row ids with an open pending schedule on the server */
  pendingTruckRowIds: Set<number>;
  executeAt: string;
  /** shopCode by truckRowId for duplicate detection */
  shopCodeByTruckRowId?: Map<number, string>;
};

export type ValidateScheduleMovesResult =
  | { ok: true }
  | { ok: false; errors: ScheduleMoveValidationError[] };

function laneBucketKey(lane: ScheduleLaneOption): string {
  return `${lane.truckLanceCode}|${lane.storeId}|${lane.remark ?? ""}`;
}

function findLaneForMove(
  move: TruckLaneMoveTargetRequest,
  lanes: ScheduleLaneOption[],
): ScheduleLaneOption | undefined {
  const code = move.toTruckLanceCode.trim();
  const storeId = move.toStoreId.trim();
  const remark = (move.toRemark ?? "").trim();
  return lanes.find(
    (l) =>
      l.truckLanceCode.trim() === code &&
      l.storeId.trim() === storeId &&
      (l.remark ?? "").trim() === remark,
  );
}

export function validateScheduleMoves(
  input: ValidateScheduleMovesInput,
): ValidateScheduleMovesResult {
  const errors: ScheduleMoveValidationError[] = [];
  const {
    moves,
    lanes,
    pendingTruckRowIds,
    executeAt,
    shopCodeByTruckRowId = new Map(),
  } = input;

  if (moves.length === 0) {
    errors.push({
      truckRowId: 0,
      messageKey: "schedule_err_no_moves",
    });
  }

  if (isExecuteAtTooEarly(executeAt)) {
    errors.push({
      truckRowId: 0,
      messageKey: "schedule_err_execute_at_past",
    });
  }

  const shopCodesInTargetBucket = new Map<string, Set<string>>();

  for (const move of moves) {
    const truckRowId = move.truckRowId;

    if (pendingTruckRowIds.has(truckRowId)) {
      errors.push({
        truckRowId,
        messageKey: "schedule_err_open_pending",
        messageParams: { id: truckRowId },
      });
    }

    const lane = findLaneForMove(move, lanes);
    if (!lane) {
      errors.push({
        truckRowId,
        messageKey: "schedule_err_target_lane_missing",
        messageParams: { lane: move.toTruckLanceCode },
      });
      continue;
    }

    const persistedShops = lane.shops.filter((s) => s.truckRowId > 0);
    if (persistedShops.length === 0) {
      errors.push({
        truckRowId,
        messageKey: "schedule_err_target_lane_empty",
        messageParams: { lane: lane.label },
      });
      continue;
    }

    const shopCode = shopCodeByTruckRowId.get(truckRowId)?.trim();
    if (shopCode) {
      const bucket = laneBucketKey(lane);
      const duplicateOnLane = lane.shops.some((s) => {
        if (s.truckRowId === truckRowId) return false;
        const otherCode = shopCodeByTruckRowId.get(s.truckRowId)?.trim();
        return otherCode === shopCode;
      });
      const seen = shopCodesInTargetBucket.get(bucket) ?? new Set<string>();
      const duplicateInBatch = seen.has(shopCode);

      if (duplicateOnLane || duplicateInBatch) {
        errors.push({
          truckRowId,
          messageKey: "schedule_err_duplicate_shop",
          messageParams: { shop: shopCode },
        });
      } else {
        seen.add(shopCode);
        shopCodesInTargetBucket.set(bucket, seen);
      }
    }
  }

  if (errors.length > 0) {
    return { ok: false, errors };
  }
  return { ok: true };
}
