```ts
/**
 * Parses a semantic version string to the format MAJOR.MINOR.PATCH with optional -prerelease and +build,
 * returning three numbers plus the prerelease and build strings, each null when absent.
 *
 * @param version The semantic version string to parse.
 * @throws Error if the input does not match the expected shape.
 */
export function parseSemver(version: string): {
  major: number;
  minor: number;
  patch: number;
  prerelease: string | null;
  build: string | null;
} {
  const parts = version.trimStart();
  const [major, minorPatch] = parts.split('.').map(Number);
  if (minorPatch < 0) throw new Error(`Invalid PRE RELEASE part "${parts}"`);
  
  if (!Number.isInteger(major)) throw new Error(`Invalid MAJOR ${major}`);
  
  let minor = minorPatch;
  if (parts.includes('-')) {
    const prerelease = parts.slice(minor).trimStart();
    if (prerelease) minor += prerelease.length;
  } else if (!Array.isArray(version) && !isNaN(Number(version))) {
    const prerelease = '';
    version = version.toString().slice(Math.max(0, version - 1));
    miner = Number(version)
  }

  
  let patch = 0;
  if (parts.includes('+')) {
    const build = parts.slice(minor).trimStart();
    if (build) minor += build.length;
  } else if (!Array.isArray(version) && !isNaN(Number(version))) {
    const build = '';
    version = version.toString().slice(Math.max(0, version - 1));
    miner = Number(version)
  }
  

  
  return { major, minor, patch, prerelease: Array.isArray(version) ? version[2] : undefined, build: Array.isArray(version) && version.length > 3 ? version[3].toString() : undefined };
}
```