// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { assert } from 'chai';
import { size } from '../../util/iterables.std.ts';

import {
  maybeParseUrl,
  setUrlSearchParams,
  urlPathFromComponents,
} from '../../util/url.std.ts';

describe('URL utilities', () => {
  describe('maybeParseUrl', () => {
    it('parses valid URLs', () => {
      [
        'https://example.com',
        'https://example.com:123/pathname?query=string#hash',
        'file:///path/to/file.txt',
      ].forEach(href => {
        assert.deepEqual(maybeParseUrl(href), new URL(href));
      });
    });

    it('returns undefined for invalid URLs', () => {
      ['', 'example.com'].forEach(href => {
        assert.isUndefined(maybeParseUrl(href));
      });
    });

    it('handles non-strings for compatibility, returning undefined', () => {
      [undefined, null, 123, ['https://example.com']].forEach(value => {
        // oxlint-disable-next-line typescript/no-explicit-any
        assert.isUndefined(maybeParseUrl(value as any));
      });
    });
  });

  describe('setUrlSearchParams', () => {
    it('returns a new URL with updated search params', () => {
      const params = {
        normal_string: 'foo',
        empty_string: '',
        number: 123,
        true_bool: true,
        false_bool: false,
        array: ['ok', 'wow'],
        stringified: { toString: () => 'bar' },
      };

      const newUrl = setUrlSearchParams(
        new URL('https://example.com/path?should_be=overwritten#hash'),
        params
      );

      assert(newUrl.href.startsWith('https://example.com/path?'));
      assert.strictEqual(newUrl.hash, '#hash');

      assert.strictEqual(
        size(newUrl.searchParams.entries()),
        Object.keys(params).length
      );
      assert.strictEqual(newUrl.searchParams.get('normal_string'), 'foo');
      assert.strictEqual(newUrl.searchParams.get('empty_string'), '');
      assert.strictEqual(newUrl.searchParams.get('number'), '123');
      assert.strictEqual(newUrl.searchParams.get('true_bool'), 'true');
      assert.strictEqual(newUrl.searchParams.get('false_bool'), 'false');
      assert.strictEqual(newUrl.searchParams.get('array'), 'ok,wow');
      assert.strictEqual(newUrl.searchParams.get('stringified'), 'bar');
    });

    it("doesn't touch the original URL or its params", () => {
      const originalHref = 'https://example.com/path?query=string';
      const originalUrl = new URL(originalHref);

      const params = { foo: 'bar' };

      const newUrl = setUrlSearchParams(originalUrl, params);

      assert.notStrictEqual(originalUrl, newUrl);
      assert.strictEqual(originalUrl.href, originalHref);

      params.foo = 'should be ignored';
      assert.strictEqual(newUrl.search, '?foo=bar');
    });
  });

  describe('urlPathFromComponents', () => {
    it('returns / if no components are provided', () => {
      assert.strictEqual(urlPathFromComponents([]), '/');
    });

    it('joins components, percent-encoding them and removing empty components', () => {
      const components = ['foo', '', '~', 'bar / baz qúx'];
      assert.strictEqual(
        urlPathFromComponents(components),
        '/foo/~/bar%20%2F%20baz%20q%C3%BAx'
      );
    });
  });
});
