import type { MockInstance } from "vitest";
import { describe, beforeEach, afterEach, it, expect, vi } from "vitest";
import log from "../../../log";
import type Period from "../period";
import { MANIFEST_UPDATE_TYPE } from "../types";
import updatePeriodInPlace from "../update_period_in_place";

const oldVideoRepresentation1 = {
  id: "rep-video-1",
  getMetadataSnapshot() {
    return { id: "rep-video-1", bitrate: 5 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};
const oldVideoRepresentation2 = {
  id: "rep-video-2",
  getMetadataSnapshot() {
    return { id: "rep-video-2", bitrate: 6 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};
const oldVideoRepresentation3 = {
  id: "rep-video-3",
  getMetadataSnapshot() {
    return { id: "rep-video-3", bitrate: 7 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};
const oldVideoRepresentation4 = {
  id: "rep-video-4",
  getMetadataSnapshot() {
    return { id: "rep-video-4", bitrate: 8 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};
const oldAudioRepresentation = {
  id: "rep-audio-1",
  getMetadataSnapshot() {
    return { id: "rep-audio-1", bitrate: 65 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};

const newVideoRepresentation1 = {
  id: "rep-video-1",
  getMetadataSnapshot() {
    return { id: "rep-video-2", bitrate: 11 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};
const newVideoRepresentation2 = {
  id: "rep-video-2",
  getMetadataSnapshot() {
    return { id: "rep-video-2", bitrate: 12 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};
const newVideoRepresentation3 = {
  id: "rep-video-3",
  getMetadataSnapshot() {
    return { id: "rep-video-3", bitrate: 13 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};
const newVideoRepresentation4 = {
  id: "rep-video-4",
  getMetadataSnapshot() {
    return { id: "rep-video-4", bitrate: 14 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};
const newAudioRepresentation = {
  id: "rep-audio-1",
  getMetadataSnapshot() {
    return { id: "rep-audio-1", bitrate: 69 };
  },
  index: {
    _update() {
      /* noop */
    },
    _replace() {
      /* noop */
    },
  },
};

function generateFakeAdaptation({
  type,
  id,
  representations,
}: {
  type: string;
  id: string;
  representations: unknown[];
}) {
  return {
    id,
    type,
    getMetadataSnapshot() {
      return { id, type, representations };
    },
    representations,
  };
}

function generateFakeThumbnailTrack({ id }: { id: string }) {
  return {
    id,
    mimeType: "image/png",
    height: 100,
    width: 200,
    horizontalTiles: 5,
    verticalTiles: 3,
    start: 0,
    end: 100,
    tileDuration: 2,
    index: {
      _update() {
        /* noop */
      },
      _replace() {
        /* noop */
      },
    },
  };
}

describe("Manifest - updatePeriodInPlace", () => {
  let mockOldVideoRepresentation1Replace: MockInstance | undefined;
  let mockOldVideoRepresentation2Replace: MockInstance | undefined;
  let mockOldVideoRepresentation3Replace: MockInstance | undefined;
  let mockOldVideoRepresentation4Replace: MockInstance | undefined;
  let mockOldAudioRepresentationReplace: MockInstance | undefined;
  let mockOldVideoRepresentation1Update: MockInstance | undefined;
  let mockOldVideoRepresentation2Update: MockInstance | undefined;
  let mockOldVideoRepresentation3Update: MockInstance | undefined;
  let mockOldVideoRepresentation4Update: MockInstance | undefined;
  let mockOldAudioRepresentationUpdate: MockInstance | undefined;
  let mockNewVideoRepresentation1Replace: MockInstance | undefined;
  let mockNewVideoRepresentation2Replace: MockInstance | undefined;
  let mockNewVideoRepresentation3Replace: MockInstance | undefined;
  let mockNewVideoRepresentation4Replace: MockInstance | undefined;
  let mockNewAudioRepresentationReplace: MockInstance | undefined;
  let mockNewVideoRepresentation1Update: MockInstance | undefined;
  let mockNewVideoRepresentation2Update: MockInstance | undefined;
  let mockNewVideoRepresentation3Update: MockInstance | undefined;
  let mockNewVideoRepresentation4Update: MockInstance | undefined;
  let mockNewAudioRepresentationUpdate: MockInstance | undefined;

  beforeEach(() => {
    mockOldVideoRepresentation1Replace = vi.spyOn(
      oldVideoRepresentation1.index,
      "_replace",
    );
    mockOldVideoRepresentation2Replace = vi.spyOn(
      oldVideoRepresentation2.index,
      "_replace",
    );
    mockOldVideoRepresentation3Replace = vi.spyOn(
      oldVideoRepresentation3.index,
      "_replace",
    );
    mockOldVideoRepresentation4Replace = vi.spyOn(
      oldVideoRepresentation4.index,
      "_replace",
    );
    mockOldAudioRepresentationReplace = vi.spyOn(
      oldAudioRepresentation.index,
      "_replace",
    );
    mockOldVideoRepresentation1Update = vi.spyOn(
      oldVideoRepresentation1.index,
      "_update",
    );
    mockOldVideoRepresentation2Update = vi.spyOn(
      oldVideoRepresentation2.index,
      "_update",
    );
    mockOldVideoRepresentation3Update = vi.spyOn(
      oldVideoRepresentation3.index,
      "_update",
    );
    mockOldVideoRepresentation4Update = vi.spyOn(
      oldVideoRepresentation4.index,
      "_update",
    );
    mockOldAudioRepresentationUpdate = vi.spyOn(oldAudioRepresentation.index, "_update");
    mockNewVideoRepresentation1Replace = vi.spyOn(
      newVideoRepresentation1.index,
      "_replace",
    );
    mockNewVideoRepresentation2Replace = vi.spyOn(
      newVideoRepresentation2.index,
      "_replace",
    );
    mockNewVideoRepresentation3Replace = vi.spyOn(
      newVideoRepresentation3.index,
      "_replace",
    );
    mockNewVideoRepresentation4Replace = vi.spyOn(
      newVideoRepresentation4.index,
      "_replace",
    );
    mockNewAudioRepresentationReplace = vi.spyOn(
      newAudioRepresentation.index,
      "_replace",
    );
    mockNewVideoRepresentation1Update = vi.spyOn(
      newVideoRepresentation1.index,
      "_update",
    );
    mockNewVideoRepresentation2Update = vi.spyOn(
      newVideoRepresentation2.index,
      "_update",
    );
    mockNewVideoRepresentation3Update = vi.spyOn(
      newVideoRepresentation3.index,
      "_update",
    );
    mockNewVideoRepresentation4Update = vi.spyOn(
      newVideoRepresentation4.index,
      "_update",
    );
    mockNewAudioRepresentationUpdate = vi.spyOn(newAudioRepresentation.index, "_update");
  });

  afterEach(() => {
    mockOldVideoRepresentation1Replace?.mockRestore();
    mockOldVideoRepresentation2Replace?.mockRestore();
    mockOldVideoRepresentation3Replace?.mockRestore();
    mockOldVideoRepresentation4Replace?.mockRestore();
    mockOldAudioRepresentationReplace?.mockRestore();
    mockOldVideoRepresentation1Update?.mockRestore();
    mockOldVideoRepresentation2Update?.mockRestore();
    mockOldVideoRepresentation3Update?.mockRestore();
    mockOldVideoRepresentation4Update?.mockRestore();
    mockOldAudioRepresentationUpdate?.mockRestore();
    mockNewVideoRepresentation1Replace?.mockRestore();
    mockNewVideoRepresentation2Replace?.mockRestore();
    mockNewVideoRepresentation3Replace?.mockRestore();
    mockNewVideoRepresentation4Replace?.mockRestore();
    mockNewAudioRepresentationReplace?.mockRestore();
    mockNewVideoRepresentation1Update?.mockRestore();
    mockNewVideoRepresentation2Update?.mockRestore();
    mockNewVideoRepresentation3Update?.mockRestore();
    mockNewVideoRepresentation4Update?.mockRestore();
    mockNewAudioRepresentationUpdate?.mockRestore();
  });

  it("should fully update the first Period given by the second one in a full update", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1, oldVideoRepresentation2],
    });
    const oldVideoAdaptation2 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-2",
      representations: [oldVideoRepresentation3, oldVideoRepresentation4],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1, oldVideoAdaptation2],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldVideoAdaptation2, oldAudioAdaptation];
      },
    };
    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1, newVideoRepresentation2],
    });
    const newVideoAdaptation2 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-2",
      representations: [newVideoRepresentation3, newVideoRepresentation4],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1, newVideoAdaptation2],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newVideoAdaptation2, newAudioAdaptation];
      },
    };

    const oldPeriodAdaptations = vi.spyOn(oldPeriod, "getAdaptations");
    const newPeriodAdaptations = vi.spyOn(newPeriod, "getAdaptations");
    const mockLog = vi.spyOn(log, "warn");

    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Full,
    );

    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [
            oldVideoRepresentation1.getMetadataSnapshot(),
            oldVideoRepresentation2.getMetadataSnapshot(),
          ],
        },
        {
          adaptation: "ada-video-2",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [
            oldVideoRepresentation3.getMetadataSnapshot(),
            oldVideoRepresentation4.getMetadataSnapshot(),
          ],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });

    expect(oldPeriod.start).toEqual(500);
    expect(oldPeriod.end).toEqual(520);
    expect(oldPeriod.duration).toEqual(20);
    expect(oldPeriodAdaptations).toHaveBeenCalledTimes(1);

    expect(mockOldVideoRepresentation1Replace).toHaveBeenCalledTimes(1);
    expect(mockOldVideoRepresentation1Replace).toHaveBeenCalledWith(
      newVideoRepresentation1.index,
    );

    expect(mockOldVideoRepresentation2Replace).toHaveBeenCalledTimes(1);
    expect(mockOldVideoRepresentation2Replace).toHaveBeenCalledWith(
      newVideoRepresentation2.index,
    );

    expect(mockOldVideoRepresentation3Replace).toHaveBeenCalledTimes(1);
    expect(mockOldVideoRepresentation3Replace).toHaveBeenCalledWith(
      newVideoRepresentation3.index,
    );

    expect(mockOldVideoRepresentation4Replace).toHaveBeenCalledTimes(1);
    expect(mockOldVideoRepresentation4Replace).toHaveBeenCalledWith(
      newVideoRepresentation4.index,
    );

    expect(mockOldAudioRepresentationReplace).toHaveBeenCalledTimes(1);
    expect(mockOldAudioRepresentationReplace).toHaveBeenCalledWith(
      newAudioRepresentation.index,
    );

    expect(newPeriod.start).toEqual(500);
    expect(newPeriod.end).toEqual(520);
    expect(newPeriod.duration).toEqual(20);
    expect(newPeriodAdaptations).toHaveBeenCalledTimes(1);

    expect(mockNewVideoRepresentation1Replace).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation2Replace).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation3Replace).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation4Replace).not.toHaveBeenCalled();
    expect(mockNewAudioRepresentationReplace).not.toHaveBeenCalled();

    expect(mockOldVideoRepresentation1Update).not.toHaveBeenCalled();
    expect(mockOldVideoRepresentation2Update).not.toHaveBeenCalled();
    expect(mockOldVideoRepresentation3Update).not.toHaveBeenCalled();
    expect(mockOldVideoRepresentation4Update).not.toHaveBeenCalled();
    expect(mockOldAudioRepresentationUpdate).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation1Update).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation2Update).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation3Update).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation4Update).not.toHaveBeenCalled();
    expect(mockNewAudioRepresentationUpdate).not.toHaveBeenCalled();

    expect(mockLog).toHaveBeenCalledTimes(0);
    mockLog.mockRestore();
  });

  it("should partially update the first Period given by the second one in a partial update", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1, oldVideoRepresentation2],
    });
    const oldVideoAdaptation2 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-2",
      representations: [oldVideoRepresentation3, oldVideoRepresentation4],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1, oldVideoAdaptation2],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldVideoAdaptation2, oldAudioAdaptation];
      },
    };
    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1, newVideoRepresentation2],
    });
    const newVideoAdaptation2 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-2",
      representations: [newVideoRepresentation3, newVideoRepresentation4],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1, newVideoAdaptation2],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newVideoAdaptation2, newAudioAdaptation];
      },
    };

    const mockOldPeriodGetAdaptations = vi.spyOn(oldPeriod, "getAdaptations");
    const mockNewPeriodGetAdaptations = vi.spyOn(newPeriod, "getAdaptations");
    const mockLog = vi.spyOn(log, "warn");

    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Partial,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [
            oldVideoRepresentation1.getMetadataSnapshot(),
            oldVideoRepresentation2.getMetadataSnapshot(),
          ],
        },
        {
          adaptation: "ada-video-2",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [
            oldVideoRepresentation3.getMetadataSnapshot(),
            oldVideoRepresentation4.getMetadataSnapshot(),
          ],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });

    expect(oldPeriod.start).toEqual(500);
    expect(oldPeriod.end).toEqual(520);
    expect(oldPeriod.duration).toEqual(20);
    expect(mockOldPeriodGetAdaptations).toHaveBeenCalledTimes(1);

    expect(mockOldVideoRepresentation1Update).toHaveBeenCalledTimes(1);
    expect(mockOldVideoRepresentation1Update).toHaveBeenCalledWith(
      newVideoRepresentation1.index,
    );

    expect(mockOldVideoRepresentation2Update).toHaveBeenCalledTimes(1);
    expect(mockOldVideoRepresentation2Update).toHaveBeenCalledWith(
      newVideoRepresentation2.index,
    );

    expect(mockOldVideoRepresentation3Update).toHaveBeenCalledTimes(1);
    expect(mockOldVideoRepresentation3Update).toHaveBeenCalledWith(
      newVideoRepresentation3.index,
    );

    expect(mockOldVideoRepresentation4Update).toHaveBeenCalledTimes(1);
    expect(mockOldVideoRepresentation4Update).toHaveBeenCalledWith(
      newVideoRepresentation4.index,
    );

    expect(mockOldAudioRepresentationUpdate).toHaveBeenCalledTimes(1);
    expect(mockOldAudioRepresentationUpdate).toHaveBeenCalledWith(
      newAudioRepresentation.index,
    );

    expect(newPeriod.start).toEqual(500);
    expect(newPeriod.end).toEqual(520);
    expect(newPeriod.duration).toEqual(20);
    expect(mockNewPeriodGetAdaptations).toHaveBeenCalledTimes(1);

    expect(mockNewVideoRepresentation1Update).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation2Update).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation3Update).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation4Update).not.toHaveBeenCalled();
    expect(mockNewAudioRepresentationUpdate).not.toHaveBeenCalled();

    expect(mockOldVideoRepresentation1Replace).not.toHaveBeenCalled();
    expect(mockOldVideoRepresentation2Replace).not.toHaveBeenCalled();
    expect(mockOldVideoRepresentation3Replace).not.toHaveBeenCalled();
    expect(mockOldVideoRepresentation4Replace).not.toHaveBeenCalled();
    expect(mockOldAudioRepresentationReplace).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation1Replace).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation2Replace).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation3Replace).not.toHaveBeenCalled();
    expect(mockNewVideoRepresentation4Replace).not.toHaveBeenCalled();
    expect(mockNewAudioRepresentationReplace).not.toHaveBeenCalled();

    expect(mockLog).toHaveBeenCalledTimes(0);
    mockLog.mockRestore();
  });

  it("should add new Adaptations in Full mode", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1, oldVideoRepresentation2],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldAudioAdaptation];
      },
      getMetadataSnapshot() {
        return {};
      },
    };
    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1, newVideoRepresentation2],
    });
    const newVideoAdaptation2 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-2",
      representations: [newVideoRepresentation3, newVideoRepresentation4],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1, newVideoAdaptation2],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newVideoAdaptation2, newAudioAdaptation];
      },
      getMetadataSnapshot() {
        return {};
      },
    };

    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Full,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [newVideoAdaptation2.getMetadataSnapshot()],
      removedAdaptations: [],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [
            oldVideoRepresentation1.getMetadataSnapshot(),
            oldVideoRepresentation2.getMetadataSnapshot(),
          ],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });
    expect(mockLog).toHaveBeenCalled();
    expect(mockLog).toHaveBeenNthCalledWith(
      1,
      "manifest",
      "1 new Adaptations found when merging.",
    );
    expect(oldPeriod.adaptations.video).toHaveLength(2);
    mockLog.mockRestore();
  });

  it("should add new Adaptations in Partial mode", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1, oldVideoRepresentation2],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldAudioAdaptation];
      },
    };
    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1, newVideoRepresentation2],
    });
    const newVideoAdaptation2 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-2",
      representations: [newVideoRepresentation3, newVideoRepresentation4],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1, newVideoAdaptation2],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newVideoAdaptation2, newAudioAdaptation];
      },
      getMetadataSnapshot() {
        return {};
      },
    };

    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Partial,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [newVideoAdaptation2.getMetadataSnapshot()],
      removedAdaptations: [],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [
            oldVideoRepresentation1.getMetadataSnapshot(),
            oldVideoRepresentation2.getMetadataSnapshot(),
          ],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });
    expect(mockLog).toHaveBeenCalled();
    expect(mockLog).toHaveBeenNthCalledWith(
      1,
      "manifest",
      "1 new Adaptations found when merging.",
    );
    expect(oldPeriod.adaptations.video).toHaveLength(2);
    mockLog.mockRestore();
  });

  it("should remove unfound Adaptations in Full mode", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1, oldVideoRepresentation2],
    });
    const oldVideoAdaptation2 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-2",
      representations: [oldVideoRepresentation3, oldVideoRepresentation4],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1, oldVideoAdaptation2],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldVideoAdaptation2, oldAudioAdaptation];
      },
    };
    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1, newVideoRepresentation2],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newAudioAdaptation];
      },
      getMetadataSnapshot() {
        return {};
      },
    };

    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Full,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [
        {
          id: "ada-video-2",
          trackType: "video",
        },
      ],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [
            oldVideoRepresentation1.getMetadataSnapshot(),
            oldVideoRepresentation2.getMetadataSnapshot(),
          ],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });
    expect(mockLog).toHaveBeenCalled();
    expect(mockLog).toHaveBeenNthCalledWith(
      1,
      "manifest",
      'Adaptation "ada-video-2" not found when merging.',
    );
    expect(oldPeriod.adaptations.video).toHaveLength(2);
    mockLog.mockRestore();
  });

  it("should remove unfound Adaptations in Partial mode", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1, oldVideoRepresentation2],
    });
    const oldVideoAdaptation2 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-2",
      representations: [oldVideoRepresentation3, oldVideoRepresentation4],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1, oldVideoAdaptation2],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldVideoAdaptation2, oldAudioAdaptation];
      },
    };
    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1, newVideoRepresentation2],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newAudioAdaptation];
      },
      getMetadataSnapshot() {
        return {};
      },
    };

    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Partial,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [
        {
          id: "ada-video-2",
          trackType: "video",
        },
      ],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [
            oldVideoRepresentation1.getMetadataSnapshot(),
            oldVideoRepresentation2.getMetadataSnapshot(),
          ],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });
    expect(mockLog).toHaveBeenCalled();
    expect(mockLog).toHaveBeenNthCalledWith(
      1,
      "manifest",
      'Adaptation "ada-video-2" not found when merging.',
    );
    expect(oldPeriod.adaptations.video).toHaveLength(2);
    mockLog.mockRestore();
  });

  it("should add new Representations in Full mode", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldAudioAdaptation];
      },
    };

    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1, newVideoRepresentation2],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newAudioAdaptation];
      },
      getMetadataSnapshot() {
        return {};
      },
    };

    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Full,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [newVideoRepresentation2.getMetadataSnapshot()],
          removedRepresentations: [],
          updatedRepresentations: [oldVideoRepresentation1.getMetadataSnapshot()],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });
    expect(mockLog).toHaveBeenCalledTimes(1);
    expect(mockLog).toHaveBeenCalledWith(
      "manifest",
      "1 new Representations found when merging.",
    );
    expect(oldVideoAdaptation1.representations).toHaveLength(2);
    mockLog.mockRestore();
  });

  it("should add new Representations in Partial mode", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldAudioAdaptation];
      },
    };

    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1, newVideoRepresentation2],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newAudioAdaptation];
      },
      getMetadataSnapshot() {
        return {};
      },
    };

    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Partial,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [newVideoRepresentation2.getMetadataSnapshot()],
          removedRepresentations: [],
          updatedRepresentations: [oldVideoRepresentation1.getMetadataSnapshot()],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });
    expect(mockLog).toHaveBeenCalledTimes(1);
    expect(mockLog).toHaveBeenCalledWith(
      "manifest",
      "1 new Representations found when merging.",
    );
    expect(oldVideoAdaptation1.representations).toHaveLength(2);
    mockLog.mockRestore();
  });

  it("should remove an old Representation that is not found in Full mode", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1, oldVideoRepresentation2],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldAudioAdaptation];
      },
    };
    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newAudioAdaptation];
      },
      getMetadataSnapshot() {
        return {};
      },
    };

    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Full,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [oldVideoRepresentation2.id],
          updatedRepresentations: [oldVideoRepresentation1.getMetadataSnapshot()],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });
    expect(mockLog).toHaveBeenCalledTimes(1);
    expect(mockLog).toHaveBeenCalledWith(
      "manifest",
      'Representation "rep-video-2" not found when merging.',
    );
    expect(oldVideoAdaptation1.representations).toHaveLength(1);
    mockLog.mockRestore();
  });

  it("should remove an old Representation that is not found in Partial mode", () => {
    const oldVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [oldVideoRepresentation1, oldVideoRepresentation2],
    });
    const oldAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [oldAudioRepresentation],
    });
    const oldPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [],
      adaptations: {
        video: [oldVideoAdaptation1],
        audio: [oldAudioAdaptation],
      },
      getAdaptations() {
        return [oldVideoAdaptation1, oldAudioAdaptation];
      },
    };
    const newVideoAdaptation1 = generateFakeAdaptation({
      type: "video",
      id: "ada-video-1",
      representations: [newVideoRepresentation1],
    });
    const newAudioAdaptation = generateFakeAdaptation({
      type: "audio",
      id: "ada-audio-1",
      representations: [newAudioRepresentation],
    });
    const newPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [],
      adaptations: {
        video: [newVideoAdaptation1],
        audio: [newAudioAdaptation],
      },
      getAdaptations() {
        return [newVideoAdaptation1, newAudioAdaptation];
      },
      getMetadataSnapshot() {
        return {};
      },
    };

    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Partial,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [
        {
          adaptation: "ada-video-1",
          trackType: "video",
          addedRepresentations: [],
          removedRepresentations: [oldVideoRepresentation2.id],
          updatedRepresentations: [oldVideoRepresentation1.getMetadataSnapshot()],
        },
        {
          adaptation: "ada-audio-1",
          trackType: "audio",
          addedRepresentations: [],
          removedRepresentations: [],
          updatedRepresentations: [oldAudioRepresentation.getMetadataSnapshot()],
        },
      ],
    });
    expect(mockLog).toHaveBeenCalledTimes(1);
    expect(mockLog).toHaveBeenCalledWith(
      "manifest",
      'Representation "rep-video-2" not found when merging.',
    );
    expect(oldVideoAdaptation1.representations).toHaveLength(1);
    mockLog.mockRestore();
  });

  it("should add new Thumbnail Track in Full mode", () => {
    const oldThumbnailTrack1 = generateFakeThumbnailTrack({
      id: "thumb-1",
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [oldThumbnailTrack1],
      adaptations: {
        video: [],
        audio: [],
      },
      getAdaptations() {
        return [];
      },
    };
    const newThumbnailTrack1 = generateFakeThumbnailTrack({
      id: "thumb-1",
    });
    const newThumbnailTrack2 = generateFakeThumbnailTrack({
      id: "thumb-2",
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [newThumbnailTrack1, newThumbnailTrack2],
      adaptations: {
        video: [],
        audio: [],
      },
      getAdaptations() {
        return [];
      },
    };
    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Full,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [
        {
          height: 100,
          horizontalTiles: 5,
          id: "thumb-2",
          mimeType: "image/png",
          verticalTiles: 3,
          start: 0,
          end: 100,
          tileDuration: 2,
          width: 200,
        },
      ],
      updatedThumbnailTracks: [
        {
          height: 100,
          horizontalTiles: 5,
          id: "thumb-1",
          mimeType: "image/png",
          verticalTiles: 3,
          start: 0,
          end: 100,
          tileDuration: 2,
          width: 200,
        },
      ],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [],
    });
    expect(mockLog).toHaveBeenCalled();
    expect(mockLog).toHaveBeenNthCalledWith(
      1,
      "manifest",
      "1 new Thumbnail tracks found when merging.",
    );
    expect(oldPeriod.thumbnailTracks).toHaveLength(2);
    mockLog.mockRestore();
  });

  it("should add new Thumbnail Track in Partial mode", () => {
    const oldThumbnailTrack1 = generateFakeThumbnailTrack({
      id: "thumb-1",
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [oldThumbnailTrack1],
      adaptations: {
        video: [],
        audio: [],
      },
      getAdaptations() {
        return [];
      },
    };
    const newThumbnailTrack1 = generateFakeThumbnailTrack({
      id: "thumb-1",
    });
    const newThumbnailTrack2 = generateFakeThumbnailTrack({
      id: "thumb-2",
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [newThumbnailTrack1, newThumbnailTrack2],
      adaptations: {
        video: [],
        audio: [],
      },
      getAdaptations() {
        return [];
      },
    };
    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Partial,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [
        {
          height: 100,
          horizontalTiles: 5,
          id: "thumb-2",
          mimeType: "image/png",
          verticalTiles: 3,
          start: 0,
          end: 100,
          tileDuration: 2,
          width: 200,
        },
      ],
      updatedThumbnailTracks: [
        {
          height: 100,
          horizontalTiles: 5,
          id: "thumb-1",
          mimeType: "image/png",
          verticalTiles: 3,
          start: 0,
          end: 100,
          tileDuration: 2,
          width: 200,
        },
      ],
      removedThumbnailTracks: [],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [],
    });
    expect(mockLog).toHaveBeenCalled();
    expect(mockLog).toHaveBeenNthCalledWith(
      1,
      "manifest",
      "1 new Thumbnail tracks found when merging.",
    );
    expect(oldPeriod.thumbnailTracks).toHaveLength(2);
    mockLog.mockRestore();
  });

  it("should remove unfound Thumbnail Tracks in Full mode", () => {
    const oldThumbnailTrack1 = generateFakeThumbnailTrack({
      id: "thumb-1",
    });
    const oldThumbnailTrack2 = generateFakeThumbnailTrack({
      id: "thumb-2",
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [oldThumbnailTrack1, oldThumbnailTrack2],
      adaptations: {
        video: [],
        audio: [],
      },
      getAdaptations() {
        return [];
      },
    };
    const newThumbnailTrack1 = generateFakeThumbnailTrack({
      id: "thumb-1",
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [newThumbnailTrack1],
      adaptations: {
        video: [],
        audio: [],
      },
      getAdaptations() {
        return [];
      },
    };
    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Full,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [
        {
          height: 100,
          horizontalTiles: 5,
          id: "thumb-1",
          mimeType: "image/png",
          verticalTiles: 3,
          start: 0,
          end: 100,
          tileDuration: 2,
          width: 200,
        },
      ],
      removedThumbnailTracks: [
        {
          id: oldThumbnailTrack2.id,
        },
      ],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [],
    });
    expect(mockLog).toHaveBeenCalled();
    expect(mockLog).toHaveBeenNthCalledWith(
      1,
      "manifest",
      'ThumbnailTrack "thumb-2" not found when merging.',
    );
    expect(oldPeriod.thumbnailTracks).toHaveLength(1);
    mockLog.mockRestore();
  });

  it("should remove unfound ThumbnailTracks in Partial mode", () => {
    const oldThumbnailTrack1 = generateFakeThumbnailTrack({
      id: "thumb-1",
    });
    const oldThumbnailTrack2 = generateFakeThumbnailTrack({
      id: "thumb-2",
    });
    const oldPeriod = {
      start: 5,
      end: 15,
      duration: 10,
      thumbnailTracks: [oldThumbnailTrack1, oldThumbnailTrack2],
      adaptations: {
        video: [],
        audio: [],
      },
      getAdaptations() {
        return [];
      },
    };
    const newThumbnailTrack1 = generateFakeThumbnailTrack({
      id: "thumb-1",
    });
    const newPeriod = {
      start: 500,
      end: 520,
      duration: 20,
      thumbnailTracks: [newThumbnailTrack1],
      adaptations: {
        video: [],
        audio: [],
      },
      getAdaptations() {
        return [];
      },
    };
    const mockLog = vi.spyOn(log, "warn");
    const res = updatePeriodInPlace(
      oldPeriod as unknown as Period,
      newPeriod as unknown as Period,
      MANIFEST_UPDATE_TYPE.Partial,
    );
    expect(res).toEqual({
      addedThumbnailTracks: [],
      updatedThumbnailTracks: [
        {
          height: 100,
          horizontalTiles: 5,
          id: "thumb-1",
          mimeType: "image/png",
          verticalTiles: 3,
          start: 0,
          end: 100,
          tileDuration: 2,
          width: 200,
        },
      ],
      removedThumbnailTracks: [
        {
          id: oldThumbnailTrack2.id,
        },
      ],
      addedAdaptations: [],
      removedAdaptations: [],
      updatedAdaptations: [],
    });
    expect(mockLog).toHaveBeenCalled();
    expect(mockLog).toHaveBeenNthCalledWith(
      1,
      "manifest",
      'ThumbnailTrack "thumb-2" not found when merging.',
    );
    expect(oldPeriod.thumbnailTracks).toHaveLength(1);
    mockLog.mockRestore();
  });
});
