import type {
  TruckLaneScheduleLineAction,
  TruckLaneScheduleLineRequest,
} from "@/app/api/shop/actions";
import type { ScheduleLaneOption } from "@/components/Shop/ScheduleChangeModal";
import type { ScheduleDragWorkspaceState } from "@/components/Shop/scheduleDragWorkspace";
import { isExecuteAtTooEarly } from "@/components/Shop/scheduleExecuteAt";

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

export type ValidateScheduleLinesInput = {
  lines: TruckLaneScheduleLineRequest[];
  lanes: ScheduleLaneOption[];
  plannedLanes?: ScheduleDragWorkspaceState;
  pendingTruckRowIds: Set<number>;
  executeAt: string;
  shopCodeByTruckRowId?: Map<number, string>;
};

export type ValidateScheduleLinesResult =
  | { ok: true }
  | { ok: false; errors: ScheduleLineValidationError[] };

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

function lineTargetBucketKey(line: TruckLaneScheduleLineRequest): string {
  return `${line.toTruckLanceCode.trim()}|${line.toStoreId.trim()}|${(line.toRemark ?? "").trim()}`;
}

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

function effectiveShopsOnLane(
  lane: ScheduleLaneOption,
  plannedLanes?: ScheduleDragWorkspaceState,
): ScheduleLaneOption["shops"] {
  const planned = plannedLanes?.find((l) => l.id === lane.id);
  if (planned) {
    return planned.shops.map((s) => ({
      truckRowId: s.truckRowId,
      districtReferenceRaw: s.districtReferenceRaw,
      loadingSequence: s.loadingSequence,
    }));
  }
  return lane.shops;
}

export function validateScheduleLines(
  input: ValidateScheduleLinesInput,
): ValidateScheduleLinesResult {
  const errors: ScheduleLineValidationError[] = [];
  const {
    lines,
    lanes,
    plannedLanes,
    pendingTruckRowIds,
    executeAt,
    shopCodeByTruckRowId = new Map(),
  } = input;

  if (lines.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 ensuredLaneKeys = new Set(
    lines
      .filter((l) => l.action === "ENSURE_LANE")
      .map((l) => lineTargetBucketKey(l)),
  );

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

  for (const line of lines) {
    const action = line.action as TruckLaneScheduleLineAction;
    const truckRowId = line.truckRowId ?? 0;

    if (
      (action === "MOVE" || action === "DELETE") &&
      truckRowId > 0 &&
      pendingTruckRowIds.has(truckRowId)
    ) {
      errors.push({
        truckRowId,
        messageKey: "schedule_err_open_pending",
        messageParams: { id: truckRowId },
      });
    }

    if (action === "ENSURE_LANE" || action === "DELETE") {
      continue;
    }

    const lane = findLaneForLine(line, lanes);
    const targetKey = lineTargetBucketKey(line);
    const laneEnsured = ensuredLaneKeys.has(targetKey);

    if (!lane && !laneEnsured) {
      errors.push({
        truckRowId,
        messageKey: "schedule_err_target_lane_missing",
        messageParams: { lane: line.toTruckLanceCode },
      });
      continue;
    }

    if (lane) {
      const persistedShops = effectiveShopsOnLane(lane, plannedLanes).filter(
        (s) => s.truckRowId > 0,
      );
      if (persistedShops.length === 0 && !laneEnsured && action !== "CREATE") {
        errors.push({
          truckRowId,
          messageKey: "schedule_err_target_lane_empty",
          messageParams: { lane: lane.label },
        });
        continue;
      }
    }

    if (action === "MOVE" && truckRowId > 0) {
      const shopCode = shopCodeByTruckRowId.get(truckRowId)?.trim();
      if (shopCode && lane) {
        const bucket = laneBucketKey(lane);
        const laneShops = effectiveShopsOnLane(lane, plannedLanes);
        const duplicateOnLane = laneShops.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 (action === "CREATE") {
      const shopCode = line.shopCode?.trim();
      const bucketKey = targetKey;
      if (shopCode) {
        const laneForCreate = lane ?? findLaneForLine(line, lanes);
        if (laneForCreate) {
          const bucket = laneBucketKey(laneForCreate);
          const laneShops = effectiveShopsOnLane(laneForCreate, plannedLanes);
          const duplicateOnLane = laneShops.some((s) => {
            const otherCode = shopCodeByTruckRowId.get(s.truckRowId)?.trim();
            return otherCode === shopCode;
          });
          const seen = shopCodesInTargetBucket.get(bucket) ?? new Set<string>();
          if (duplicateOnLane || seen.has(shopCode)) {
            errors.push({
              truckRowId: 0,
              messageKey: "schedule_err_duplicate_shop",
              messageParams: { shop: shopCode },
            });
          } else {
            seen.add(shopCode);
            shopCodesInTargetBucket.set(bucket, seen);
          }
        } else {
          const seen = shopCodesInTargetBucket.get(bucketKey) ?? new Set<string>();
          if (seen.has(shopCode)) {
            errors.push({
              truckRowId: 0,
              messageKey: "schedule_err_duplicate_shop",
              messageParams: { shop: shopCode },
            });
          } else {
            seen.add(shopCode);
            shopCodesInTargetBucket.set(bucketKey, seen);
          }
        }
      }
    }
  }

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

export function formatScheduleLineValidationErrors(
  t: (key: string, params?: Record<string, string | number>) => string,
  errors: ScheduleLineValidationError[],
): string {
  return errors
    .map((e) => {
      const params = e.messageParams ?? {};
      return t(e.messageKey, params);
    })
    .join("\n");
}
