import { afterEach, beforeEach, describe, expect, it } from 'vitest';

import {
  installArrayAtPolyfill,
  installObjectHasOwnPolyfill,
  installPromiseWithResolversPolyfill,
  installStringAtPolyfill,
  installStructuredClonePolyfill,
  installUrlCanParsePolyfill,
} from './polyfills';

const descriptors = new Map<
  string,
  [object, PropertyKey, PropertyDescriptor | undefined]
>();

function remember(key: string, target: object, property: PropertyKey) {
  descriptors.set(key, [
    target,
    property,
    Object.getOwnPropertyDescriptor(target, property),
  ]);
}

function remove(target: object, property: PropertyKey) {
  Reflect.deleteProperty(target, property);
}

beforeEach(() => {
  remember('arrayAt', Array.prototype, 'at');
  remember('stringAt', String.prototype, 'at');
  remember('hasOwn', Object, 'hasOwn');
  remember('withResolvers', Promise, 'withResolvers');
  remember('structuredClone', globalThis, 'structuredClone');
  remember('canParse', URL, 'canParse');
});

afterEach(() => {
  for (const [target, property, descriptor] of descriptors.values()) {
    if (descriptor) Object.defineProperty(target, property, descriptor);
    else remove(target, property);
  }
  descriptors.clear();
});

describe('chrome 90 browser polyfills', () => {
  it('restores missing collection and object APIs', () => {
    remove(Array.prototype, 'at');
    remove(String.prototype, 'at');
    remove(Object, 'hasOwn');
    installArrayAtPolyfill();
    installStringAtPolyfill();
    installObjectHasOwnPolyfill();

    expect(['first', 'last'].at(-1)).toBe('last');
    expect('EasyFlow'.at(-1)).toBe('w');
    expect(Object.hasOwn({ ready: true }, 'ready')).toBe(true);
  });

  it('restores missing async, clone and URL APIs', async () => {
    remove(Promise, 'withResolvers');
    remove(globalThis, 'structuredClone');
    remove(URL, 'canParse');
    installPromiseWithResolversPolyfill();
    installStructuredClonePolyfill();
    installUrlCanParsePolyfill();

    // eslint-disable-next-line n/no-unsupported-features/es-syntax -- 测试目标就是验证该 API 的兼容实现。
    const { promise, resolve } = Promise.withResolvers<string>();
    resolve('ready');
    await expect(promise).resolves.toBe('ready');

    const source: { map: Map<string, number>; self?: unknown } = {
      map: new Map([['answer', 2026]]),
    };
    source.self = source;
    const clone = structuredClone(source);
    expect(clone.self).toBe(clone);
    expect(clone.map.get('answer')).toBe(2026);
    expect(URL.canParse('/chat', 'https://easyflow.example')).toBe(true);
  });
});
