2d HTML5 Game: How to add collision shapes to loaded tiles that might be rotated and flipped?

I have map data like this, each element separated by space is a different tile with own properties:

static get TILEMAP() {
    return "0C0003 0W1003 0W0003 0W2003 0W3003 0W4003".split(" "); // example tiles, its much longer for real maps
  }

I load tile images like this:

let tiles = {};
const tilemap = Constants.TILEMAP;

function loadTiles() {
  for (const tileIndex in tilemap) {
    const rawTile = tilemap[tileIndex].substring(0, 3); // 0W1.png for example
    const tileImg = new Image();
    tileImg.src = `sprites/maps/tiles/${rawTile}.png`;
    tiles[rawTile] = tileImg;
  }
}

And I draw them to canvas like this:

static rotationKeys = {0: 0, 1: 90, 2: 180, 3: 270};
static tileSize = 50;

function drawMap() {
  const tileSize = Constants.tileSize;
  gameSettings.mapWidth = 35;
  gameSettings.mapHeight = 24;

  const startX = Math.max(0, Math.floor(camera.x / (tileSize * scaleFactor)));
  const startY = Math.max(0, Math.floor(camera.y / (tileSize * scaleFactor)));

  const endX = Math.min(gameSettings.mapWidth, startX + Math.ceil(canvas.width / (tileSize * scaleFactor)) + 1);
  const endY = Math.min(gameSettings.mapHeight, startY + Math.ceil(canvas.height / (tileSize * scaleFactor)) + 1);

  for (let y = startY; y < endY; y++) {
    for (let x = startX; x < endX; x++) {
      let index = y * gameSettings.mapWidth + x;
      const tile = tilemap[index];
      const rotation = parseInt(tile[3]); // Get rotation value for this tile
      const flip = parseInt(tile[4]);

      // Apply rotation transformation
      ctx.save(); // Save the current canvas state
      ctx.translate((x + 0.5) * tileSize * scaleFactor, (y + 0.5) * tileSize * scaleFactor); // Translate to the center of the tile
      ctx.rotate(Constants.rotationKeys[rotation] * Math.PI / 180); // Rotate by the specified angle (converted to radians)

      // Apply flip transformation
      var xScale = 1;
      var yScale = 1;
      if (flip == 1 || flip == 3) {
        xScale = -1;
      }
      if (flip == 2 || flip == 3) {
        yScale = -1;
      }
      ctx.scale(xScale, yScale);

      try {
          ctx.drawImage(tiles[tile.substring(0, 3)], -0.5 * tileSize * scaleFactor, -0.5 * tileSize * scaleFactor, tileSize * scaleFactor, tileSize * scaleFactor); // Draw the rotated image
      } catch (error) {
          console.error(`Failed to draw tile at (${x}, ${y}):`, error);
          console.log("Responsible tile:", tile);
      }
      ctx.restore();
    }
  }
}

As you can see, the tiles can go through quite a lot. Rotations or flips or combined.

I’ve tried defining all collisions like this:

 static colTransforms = {
    "5": {
      rotations: {
        "0": { x: -1, y: -1, width: 52, height: 38,
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 38 },
          "2": { x: -1, y: 13, width: 52, height: 38 },
          "3": { x: -1, y: -1, width: 52, height: 38 }
        }},

        "1": { x: -1, y: -1, width: 38, height: 52,
        flips: {
          "1": { x: 13, y: -1, width: 38, height: 52 },
          "2": { x: -1, y: -1, width: 38, height: 52 },
          "3": { x: 13, y: -1, width: 38, height: 52 }
        }},

        "2": { x: -1, y: 13, width: 52, height: 38,
        flips: {
          "1": { x: -1, y: 13, width: 52, height: 38 },
          "2": { x: -1, y: -1, width: 52, height: 38 },
          "3": { x: -1, y: 13, width: 52, height: 38 }
        }},

        "3": { x: -1, y: -1, width: 38, height: 52,
        flips: {
          "1": { x: 13, y: -1, width: 38, height: 52 },
          "2": { x: -1, y: -1, width: 38, height: 52 },
          "3": { x: 13, y: -1, width: 38, height: 52 }
        }}
      },
    },

    "6": {
      rotations: {
        "0": { x: -1, y: -1, width: 52, height: 26,
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 26 },
          "2": { x: -1, y: 25, width: 52, height: 26 },
          "3": { x: -1, y: -1, width: 52, height: 26 }
        }},

        "1": { x: -1, y: -1, width: 26, height: 52,
        flips: {
          "1": { x: 25, y: -1, width: 26, height: 52 },
          "2": { x: -1, y: -1, width: 26, height: 52 },
          "3": { x: 25, y: -1, width: 26, height: 52 }
        }},

        "2": { x: -1, y: 25, width: 52, height: 26,
        flips: {
          "1": { x: -1, y: 25, width: 52, height: 26 },
          "2": { x: -1, y: -1, width: 52, height: 26 },
          "3": { x: -1, y: 25, width: 52, height: 26 }
        }},

        "3": { x: -1, y: -1, width: 52, height: 26,
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 26 },
          "2": { x: -1, y: 25, width: 52, height: 26 },
          "3": { x: -1, y: -1, width: 52, height: 26 }
        }}
      },
    },

    "7": {
      rotations: {
        "0": { x: -1, y: -1, width: 52, height: 13,
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 13 },
          "2": { x: -1, y: 38, width: 52, height: 13 },
          "3": { x: -1, y: -1, width: 52, height: 13 }
        }},

        "1": { x: -1, y: -1, width: 13, height: 52,
        flips: {
          "1": { x: 38, y: -1, width: 13, height: 52 },
          "2": { x: -1, y: -1, width: 13, height: 52 },
          "3": { x: 38, y: -1, width: 13, height: 52 }
        }},

        "2": { x: -1, y: 38, width: 52, height: 13,
        flips: {
          "1": { x: -1, y: 38, width: 52, height: 13 },
          "2": { x: -1, y: -1, width: 52, height: 13 },
          "3": { x: -1, y: 38, width: 52, height: 13 }
        }},

        "3": { x: -1, y: -1, width: 52, height: 13, 
        flips: {
          "1": { x: -1, y: -1, width: 52, height: 13 },
          "2": { x: -1, y: 38, width: 52, height: 13 },
          "3": { x: -1, y: -1, width: 52, height: 13 }
        }}
      },
    },

    "8": {
      rotations: {
        "0": { x: -1, y: -1, width: 26, height: 26,
        flips: {
          "1": { x: 25, y: -1, width: 26, height: 26 },
          "2": { x: -1, y: 25, width: 26, height: 26 },
          "3": { x: 25, y: -1, width: 26, height: 26 }
        }},

        "1": { x: 25, y: -1, width: 26, height: 26,
        flips: {
          "1": { x: -1, y: -1, width: 26, height: 26 },
          "2": { x: 25, y: 25, width: 26, height: 26 },
          "3": { x: -1, y: -1, width: 26, height: 26 }
        }},

        "2": { x: 25, y: 25, width: 26, height: 26,
        flips: {
          "1": { x: -1, y: 25, width: 26, height: 26 },
          "2": { x: 25, y: -1, width: 26, height: 26 },
          "3": { x: -1, y: 25, width: 26, height: 26 }
        }},

        "3": { x: -1, y: 25, width: 26, height: 26,
        flips: {
          "1": { x: 25, y: 25, width: 26, height: 26 },
          "2": { x: -1, y: -1, width: 26, height: 26 },
          "3": { x: 25, y: 25, width: 26, height: 26 }
        }}
      },
    },
    "9": {
      Lfirst: {
        rotations: {
          "0": { x: -1, y: -1, width: 52, height: 26,
          flips: {
            "1": { x: -1, y: -1, width: 52, height: 26 },
            "2": { x: -1, y: 25, width: 52, height: 26 },
            "3": { x: -1, y: -1, width: 52, height: 26 }
          }},

          "1": { x: 25, y: 25, width: 26, height: 52,
          flips: {
            "1": { x: 25, y: -1, width: 26, height: 52 },
            "2": { x: -1, y: -1, width: 26, height: 52 },
            "3": { x: 25, y: -1, width: 26, height: 52 }
          }},

          "2": { x: -1, y: 25, width: 52, height: 26,
          flips: {
            "1": { x: -1, y: 25, width: 52, height: 26 },
            "2": { x: -1, y: -1, width: 52, height: 26 },
            "3": { x: -1, y: 25, width: 52, height: 26 }
          }},

          "3": { x: -1, y: 25, width: 52, height: 26,
          flips: {
            "1": { x: -1, y: -1, width: 52, height: 26 },
            "2": { x: -1, y: 25, width: 52, height: 26 },
            "3": { x: -1, y: -1, width: 52, height: 26 }
          }}
        },
      },
      Lsecond: {
        rotations: {
          "0": { x: -1, y: -1, width: 26, height: 52,
          flips: {
            "1": { x: 25, y: -1, width: 26, height: 52 },
            "2": { x: -1, y: -1, width: 26, height: 52 },
            "3": { x: 25, y: -1, width: 26, height: 52 }
          }},

          "1": { x: -1, y: -1, width: 52, height: 26,
          flips: {
            "1": { x: -1, y: -1, width: 52, height: 26 },
            "2": { x: -1, y: 25, width: 52, height: 26 },
            "3": { x: -1, y: -1, width: 52, height: 26 }
          }},

          "2": { x: 25, y: -1, width: 26, height: 52,
          flips: {
            "1": { x: -1, y: -1, width: 26, height: 26 },
            "2": { x: 25, y: 25, width: 26, height: 26 },
            "3": { x: -1, y: -1, width: 26, height: 26 }
          }},
          "3": { x: -1, y: -1, width: 26, height: 52,
          flips: {
            "1": { x: 25, y: -1, width: 26, height: 52 },
            "2": { x: -1, y: -1, width: 26, height: 52 },
            "3": { x: 25, y: -1, width: 26, height: 52 }
          }}
        },
      }
    }
  } // transformations for collisions

So that we could later check for collision like this when the player is walking into the tile:

let newTileX;
let newTileY;

let newTileX;
    let newTileY;

    this.x = this.body.x;
    this.y = this.body.y;

    if (speedX != null) {
      newTileX = Math.floor((this.x + speedX) / 50);
    } else {
      newTileX = Math.floor(this.x / 50);
    }

    if (speedY != null) {
      newTileY = Math.floor((this.y + speedY) / 50);
    } else {
      newTileY = Math.floor(this.y / 50);
    }

    const tileName = Constants.TILEMAP[newTileY * 35 + newTileX];

    const rotation = tileName[3]; // Get rotation value for this tile
    const flip = tileName[4]; // Get flip value
    const collision = tileName[5]; // Get collision type

    let collisionShape = null;

    switch (parseInt(collision)) {
      case 0:
      case 1:
      case 2:
        // The entire tile is walkable
        collisionShape = null; // No need to define a collision shape for walkable tiles
        break;
      case 3:
      case 4:
        // The entire tile is an obstacle
        collisionShape = { x: -1, y: -1, width: 52, height: 52 };
        break;
      case 5:
      case 6:
      case 7:
      case 8:
        if (parseInt(flip) > 0) {
          collisionShape = Constants.colTransforms[collision].rotations[rotation].flips[flip];
          
        } else {
          collisionShape = Constants.colTransforms[collision].rotations[rotation];
        }
        break;
      case 9:
        if (parseInt(flip) > 0) {
          collisionShape = [];
          console.log(collision)
          collisionShape.push(Constants.colTransforms[collision].Lfirst.rotations[rotation].flips[flip]);
          collisionShape.push(Constants.colTransforms[collision].Lsecond.rotations[rotation].flips[flip]);
        } else {
          console.log(collision)
          collisionShape = [];
          collisionShape.push(Constants.colTransforms[collision].Lfirst.rotations[rotation]);
          collisionShape.push(Constants.colTransforms[collision].Lsecond.rotations[rotation]);
        }
        break
    }

    // Check for collision only if collision shape is defined
    if (collisionShape != null && !Array.isArray(collisionShape)) {
      // Calculate the player's local position within the tile
      const localPlayerX = (this.x + (speedX || 0)) % 50;
      const localPlayerY = (this.y + (speedY || 0)) % 50;

      // Calculate the center of the circular area (head radius)
      const playerCenterX = localPlayerX + Constants.playerCircle;
      const playerCenterY = localPlayerY + Constants.playerCircle;

      // Calculate the closest point on the rectangle to the circle
      const closestX = this.clamp(playerCenterX, collisionShape.x, collisionShape.x + collisionShape.width);
      const closestY = this.clamp(playerCenterY, collisionShape.y, collisionShape.y + collisionShape.height);

      // Calculate the distance between the circle's center and the closest point
      const distanceX = playerCenterX - closestX;
      const distanceY = playerCenterY - closestY;

      // Check if the distance is less than or equal to the circle's radius
      if ((distanceX * distanceX + distanceY * distanceY) <= (Constants.playerCircle * Constants.playerCircle)) {
          // Collision detected, player cannot move
          speedX = 0;
          speedY = 0;
      }
    } else if (collisionShape != null && Array.isArray(collisionShape)) {
      for (let shape of collisionShape) {
        // Calculate the player's local position within the tile
        const localPlayerX = (this.x + (speedX || 0)) % 50;
        const localPlayerY = (this.y + (speedY || 0)) % 50;

        // Calculate the center of the circular area (head radius)
        const playerCenterX = localPlayerX + Constants.playerCircle;
        const playerCenterY = localPlayerY + Constants.playerCircle;

        // Calculate the closest point on the rectangle to the circle
        const closestX = this.clamp(playerCenterX, shape.x, shape.x + shape.width);
        const closestY = this.clamp(playerCenterY, shape.y, shape.y + shape.height);

        // Calculate the distance between the circle's center and the closest point
        const distanceX = playerCenterX - closestX;
        const distanceY = playerCenterY - closestY;

        // Check if the distance is less than or equal to the circle's radius
        if ((distanceX * distanceX + distanceY * distanceY) <= (Constants.STICK_FIGURE_HEAD_RADIUS * Constants.STICK_FIGURE_HEAD_RADIUS)) {
            // Collision detected, player cannot move
            speedX = 0;
            speedY = 0;
        }
      }
    }

Its not working as expected though, and honestly the whole approach seems messy to say the least. Some shapes arent flipped or rotated properly (could be my calculations, but it also could be that the code is just doing wrong math). How to actually properly add collision shapes to the individual tiles and make sure they go through the same rotations and flips when necessary?