```ts
export function spreadNightDuty(
  crew: string[],
  weights: number[],
  away: string[][],
): string[] {
  if (crew.length === 0) {
    throw new Error("Crew cannot be empty");
  }
  if (new Set(crew).size !== crew.length) {
    throw new Error("Crew contains duplicate names");
  }
  if (!crew.every(name => typeof name === "string" && name.trim() !== "")) {
    throw new Error("Crew must contain non-empty string names");
  }
  if (weights.some(weight => weight !== 1 && weight !== 2)) {
    throw new Error("Weights must be either 1 or 2");
  }
  if (away.length !== weights.length) {
    throw new Error("Away list must be the same length as weights");
  }
  for (let night = 0; night < away.length; night++) {
    if (!Array.isArray(away[night])) {
      throw new Error("Each entry in away must be an array");
    }
    for (const name of away[night]) {
      if (!crew.includes(name)) {
        throw new Error(`Away list contains a name not in the crew: ${name}`);
      }
    }
  }

  const stood = new Map<string, number>();
  for (const name of crew) {
    stood.set(name, 0);
  }
  const nights: string[] = [];
  let previous1 = "";
  let previous2 = "";

  for (let night = 0; night < weights.length; night++) {
    let chosen = "";
    for (const name of crew) {
      if (
        away[night].includes(name) ||
        name === previous1 ||
        name === previous2
      ) {
        continue;
      }
      if (chosen === "" || stood.get(name)! < stood.get(chosen)!) {
        chosen = name;
      } else if (stood.get(name)! === stood.get(chosen)!) {
        chosen = name < chosen ? name : chosen;
      }
    }
    if (chosen === "") {
      nights.push("?");
    } else {
      stood.set(chosen, stood.get(chosen)! + weights[night]);
      nights.push(chosen);
    }
    previous2 = previous1;
    previous1 = chosen !== "" ? chosen : previous1;
  }

  return nights;
}
```