// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/src/dart/sdk/sdk_utils.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
import 'package:path/path.dart' as pathos;
import 'package:test/test.dart';

void main() {
  testGetRelativePathIfInside();
  testGetImportUriIfMatchesRelativeSdkPathSlow();
}

List<String> inputPaths = [
  "sky_engine/lib/async",
  "sky_engine/lib/collection",
  "sky_engine/lib/convert",
  "sky_engine/lib/core",
  "sky_engine/lib/developer",
  "sky_engine/lib/ffi",
  "sky_engine/lib/_http",
  "sky_engine/lib/_interceptors",
  "sky_engine/lib/internal",
  "sky_engine/lib/io",
  "sky_engine/lib/isolate",
  "sky_engine/lib/math",
  "sky_engine/lib/typed_data",
  "sky_engine/lib/ui",
  "flutter/lib",
  "project/test",
  "project/lib",
  ".pub-cache/hosted/pub.dev/foo/lib/src",
  ".pub-cache/hosted/pub.dev/foo/lib",
];

List<String> sdkPaths = [
  "sky_engine/lib/async/async.dart",
  "sky_engine/lib/collection/collection.dart",
  "sky_engine/lib/convert/convert.dart",
  "sky_engine/lib/core/core.dart",
  "sky_engine/lib/developer/developer.dart",
  "sky_engine/lib/ffi/ffi.dart",
  "sky_engine/lib/html/html_dart2js.dart",
  "sky_engine/lib/io/io.dart",
  "sky_engine/lib/isolate/isolate.dart",
  "sky_engine/lib/js/js.dart",
  "sky_engine/lib/js_util/js_util.dart",
  "sky_engine/lib/math/math.dart",
  "sky_engine/lib/typed_data/typed_data.dart",
  "sky_engine/lib/ui/ui.dart",
  "sky_engine/lib/_wasm/wasm_types.dart",
  "sky_engine/lib/_http/http.dart",
  "sky_engine/lib/_interceptors/interceptors.dart",
  "sky_engine/lib/internal/internal.dart",
  "sky_engine/lib/_empty.dart",
];

String? getImportUriIfMatchesRelativeSdkPathSlow(
  List<SdkLibrary> libraries,
  String filePath,
  String separator,
) {
  int length = libraries.length;
  List<String> paths = <String>[];
  for (int i = 0; i < length; i++) {
    SdkLibrary library = libraries[i];
    String libraryPath = library.path.replaceAll('/', separator);
    if (filePath == libraryPath) {
      return library.shortName;
    }
    paths.add(libraryPath);
  }
  for (int i = 0; i < length; i++) {
    SdkLibrary library = libraries[i];
    String libraryPath = paths[i];
    int index = libraryPath.lastIndexOf(separator);
    if (index >= 0) {
      String prefix = libraryPath.substring(0, index + 1);
      if (filePath.startsWith(prefix)) {
        String relPath = filePath
            .substring(prefix.length)
            .replaceAll(separator, '/');
        return '${library.shortName}/$relPath';
      }
    }
  }
  return null;
}

Iterable<String> getPaths(
  List<String> input, {
  required bool addFilenames,
  required String prefix,
  required String separator,
}) sync* {
  for (String s in input) {
    String base = "$prefix${s.replaceAll("/", separator)}";
    if (addFilenames) {
      yield "$base${separator}a.dart";
      yield "$base${separator}ab.dart";
    } else {
      yield base;
    }
  }
}

String? getRelativePathIfInsideSemi(
  String libraryPath,
  String filePath,
  String separator,
) {
  String libDirPath = libraryPath.substring(
    0,
    libraryPath.lastIndexOf(separator) + 1,
  );
  String fileDirPath = filePath.substring(
    0,
    filePath.lastIndexOf(separator) + 1,
  );
  if (fileDirPath.startsWith(libDirPath)) {
    return filePath.substring(libDirPath.length);
  }
  return null;
}

String? getRelativePathIfInsideSlow(
  File libraryFile,
  File file,
  pathos.Context pathContext,
) {
  var libraryFolder = libraryFile.parent;
  if (libraryFolder.contains(file.path)) {
    return pathContext.relative(file.path, from: libraryFolder.path);
  }
  return null;
}

void testGetImportUriIfMatchesRelativeSdkPathSlow() {
  List<String> knownSdkPaths = [
    "_http/http.dart",
    "async/async.dart",
    "collection/collection.dart",
    "convert/convert.dart",
    "core/core.dart",
    "developer/developer.dart",
    "ffi/ffi.dart",
    "internal/internal.dart",
    "io/io.dart",
    "isolate/isolate.dart",
    "math/math.dart",
    "typed_data/typed_data.dart",
  ];
  List<SdkLibrary> sdkLibs = knownSdkPaths
      .map(
        (path) => MockSdkLibrary(path.substring(0, path.indexOf("/")), [
          MockSdkLibraryUnit(path, ""),
        ]),
      )
      .toList();

  for (var separator in ["\\", "/"]) {
    for (var lib in sdkLibs) {
      for (var relativePathFromFile in [
        ...getPaths(
          knownSdkPaths,
          addFilenames: false,
          prefix: "",
          separator: separator,
        ),
        ...getPaths(
          knownSdkPaths.map((e) => e.substring(0, e.lastIndexOf("/"))).toList(),
          addFilenames: true,
          prefix: "",
          separator: separator,
        ),
      ]) {
        test("getImportUriIfMatchesRelativeSdkPath test "
            "lib ${lib.path} vs file $relativePathFromFile", () {
          expect(
            getImportUriIfMatchesRelativeSdkPath(
              sdkLibs,
              relativePathFromFile,
              separator,
            ),
            getImportUriIfMatchesRelativeSdkPathSlow(
              sdkLibs,
              relativePathFromFile,
              separator,
            ),
          );
        });
      }
    }
  }

  testGetImportUriIfMatchesRelativeSdkPathSlowAdditionalTests();
}

void testGetImportUriIfMatchesRelativeSdkPathSlowAdditionalTests() {
  // Try to give what's essentially invalid (extracted from
  // uri_does_not_exist_test.dart before fixing the mock sdk path in
  // mock_sdk.dart).
  var sdkLibs = [
    MockSdkLibrary("isolate", [MockSdkLibraryUnit("isolate.dart", "")]),
  ];
  var relativePathFromFile = "math/bar.dart";
  for (var separator in ["\\", "/"]) {
    for (var lib in sdkLibs) {
      test("getImportUriIfMatchesRelativeSdkPath test "
          "lib ${lib.path} vs file $relativePathFromFile", () {
        expect(
          getImportUriIfMatchesRelativeSdkPath(
            sdkLibs,
            relativePathFromFile,
            separator,
          ),
          getImportUriIfMatchesRelativeSdkPathSlow(
            sdkLibs,
            relativePathFromFile,
            separator,
          ),
        );
      });
    }
  }
}

void testGetRelativePathIfInside() {
  for (List<dynamic> osData in [
    ["C:\\windowspaths\\", "\\", pathos.windows],
    ["/posixpaths/", "/", pathos.posix],
  ]) {
    var prefix = osData[0] as String;
    var separator = osData[1] as String;
    var context = osData[2] as pathos.Context;
    var resourceProvider = MemoryResourceProvider(context: context);

    for (var libraryPath in getPaths(
      sdkPaths,
      addFilenames: false,
      prefix: prefix,
      separator: separator,
    )) {
      for (var filePath in getPaths(
        inputPaths,
        addFilenames: true,
        prefix: prefix,
        separator: separator,
      )) {
        File library = resourceProvider.getFile(libraryPath);
        File file = resourceProvider.getFile(filePath);
        test("getRelativePathIfInside test $libraryPath vs $filePath", () {
          String? relativePathIfInside = getRelativePathIfInside(
            library.path,
            file.path,
          );
          expect(
            relativePathIfInside,
            getRelativePathIfInsideSlow(
              resourceProvider.getFile(library.path),
              file,
              context,
            ),
          );
          expect(
            relativePathIfInside,
            getRelativePathIfInsideSemi(
              library.path,
              file.path,
              context.separator,
            ),
          );
        });
      }
    }
  }
}
