```ts
/**
 * Fills the desk map based on the given floor plan and legend.
 * @param plan - A non-empty list of equally long non-empty rows representing the office floor.
 * @param legend - A mapping from bank letter to a list of people who take that bank's desks.
 * @returns A mapping with three keys: "floor", "sat", and "spare".
 */
export function fillDeskMap(plan: string[], legend: Record<string, string[]>): Record<string, unknown> {
  if (!Array.isArray(plan) || plan.length === 0) {
    throw new Error("Invalid floor plan");
  }

  const rowLength = plan[0].length;
  for (const row of plan) {
    if (typeof row !== "string" || row.length === 0 || row.length !== rowLength) {
      throw new Error("Invalid row in floor plan");
    }
  }

  const validChars = new Set([...Object.keys(legend), ".", "#"]);
  for (const row of plan) {
    for (const char of row) {
      if (!validChars.has(char)) {
        throw new Error("Invalid character in floor plan");
      }
    }
  }

  for (const [key, value] of Object.entries(legend)) {
    if (key.length !== 1 || !/[a-z]/.test(key)) {
      throw new Error("Invalid legend key");
    }
    if (!plan.some(row => row.includes(key))) {
      throw new Error("Legend key not present in floor plan");
    }
    if (!Array.isArray(value)) {
      throw new Error("Invalid legend value");
    }
    for (const name of value) {
      if (typeof name !== "string" || !/^[a-zA-Z]+$/.test(name) || name.length === 0) {
        throw new Error("Invalid person name in legend");
      }
    }
  }

  const floor = plan.map(row => row.split(""));
  const sat: string[] = [];
  let spare = 0;

  for (const [bank, people] of Object.entries(legend)) {
    if (people.length > bankCount(floor, bank)) {
      throw new Error("More people than desks in a bank");
    }

    for (let i = 0; i < people.length; i++) {
      const person = people[i];
      let placed = false;

      for (let r = 0; r < floor.length; r++) {
        for (let c = 0; c < floor[r].length; c++) {
          if (floor[r][c] === bank) {
            floor[r][c] = person[0].toUpperCase();
            sat.push(`${person} r=${r} c=${c}`);
            placed = true;
            break;
          }
        }
        if (placed) break;
      }

      if (!placed) {
        throw new Error("Failed to place a person");
      }
    }

    spare += bankCount(floor, bank) - people.length;
  }

  return {
    floor: floor.map(row => row.join("")),
    sat,
    spare,
  };
}

/**
 * Counts the number of desks for a given bank in the floor plan.
 * @param floor - The floor plan as a list of rows.
 * @param bank - The bank letter to count desks for.
 * @returns The number of desks for the given bank.
 */
function bankCount(floor: string[][], bank: string): number {
  return floor.reduce((count, row) => count + row.filter(char => char === bank).length, 0);
}
```