```ts
export function mergeMarkup(spans: Array<{ start: number; end: number; tag: string }>): Array<[number, number, string]> {
  // Validate input spans
  for (const { start, end, tag } of spans) {
    if (!Number.isInteger(start) || !Number.isInteger(end)) {
      throw new Error("Start and end must be integers.");
    }
    if (start < 0) {
      throw new Error("Start must be non-negative.");
    }
    if (start >= end) {
      throw new Error("Start must be strictly below end.");
    }
    if (typeof tag !== "string" || tag.trim() === "") {
      throw new Error("Tag must be a non-empty string.");
    }
  }

  // Sort spans by start position
  spans.sort((a, b) => a.start - b.start);

  const mergedSpans: Array<[number, number, string]> = [];

  for (const span of spans) {
    const { start, end, tag } = span;
    let added = false;

    // Try to merge with the last span in mergedSpans
    if (mergedSpans.length > 0) {
      const [lastStart, lastEnd, lastTag] = mergedSpans[mergedSpans.length - 1];

      if (tag === lastTag && start <= lastEnd) {
        // Merge spans
        mergedSpans.pop();
        mergedSpans.push([lastStart, Math.max(lastEnd, end), tag]);
        added = true;
      }
    }

    if (!added) {
      mergedSpans.push([start, end, tag]);
    }
  }

  // Check for overlapping tags
  for (let i = 0; i < mergedSpans.length - 1; i++) {
    const [, end1, tag1] = mergedSpans[i];
    const [start2, , tag2] = mergedSpans[i + 1];

    if (end1 > start2 && tag1 !== tag2) {
      throw new Error("Overlapping spans with different tags.");
    }
  }

  return mergedSpans;
}
```