// Copyright (c) 2025, 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 'dart:convert';

import 'package:json_rpc_2/json_rpc_2.dart' show RpcException;

import '../constants.dart';
import '../dart_tooling_daemon.dart';

import '../response_types/response_types.dart';
import '../rpc_error_codes.dart' show RpcErrorCodes;

extension FileSystemService on DartToolingDaemon {
  /// Reads the file at [uri] from disk in the environment where the Dart
  /// Tooling Daemon is running.
  ///
  /// If [uri] is not contained in the IDE workspace roots, then an
  /// [RpcException] with [RpcErrorCodes.kPermissionDenied] is thrown.
  ///
  /// If [uri] does not exist, then an [RpcException] exception with error
  /// code [RpcErrorCodes.kFileDoesNotExist] is thrown.
  ///
  /// If [uri] does not have a file scheme, then an [RpcException] with
  /// [RpcErrorCodes.kExpectsUriParamWithFileScheme] is thrown.
  Future<FileContent> readFileAsString(
    Uri uri, {
    Encoding encoding = utf8,
  }) async {
    final result = await call(
      FileSystemServiceConstants.serviceName,
      FileSystemServiceConstants.readFileAsString,
      params: {
        DtdParameters.uri: uri.toString(),
        DtdParameters.encoding: encoding.name,
      },
    );
    return FileContent.fromDTDResponse(result);
  }

  /// Writes [contents] to the file at [uri] in the environment where the Dart
  /// Tooling Daemon is running.
  ///
  /// The file will be created if it does not exist, and it will be overwritten
  /// if it already exist.
  ///
  /// If [uri] is not contained in the IDE workspace roots, then an
  /// [RpcException] with [RpcErrorCodes.kPermissionDenied] is thrown.
  ///
  /// If [uri] does not have a file scheme, then an [RpcException] with
  /// [RpcErrorCodes.kExpectsUriParamWithFileScheme] is thrown.
  Future<void> writeFileAsString(
    Uri uri,
    String contents, {
    Encoding encoding = utf8,
  }) async {
    await call(
      FileSystemServiceConstants.serviceName,
      FileSystemServiceConstants.writeFileAsString,
      params: {
        DtdParameters.uri: uri.toString(),
        DtdParameters.contents: contents,
        DtdParameters.encoding: encoding.name,
      },
    );
  }

  /// Lists the directories and files under the directory at [uri] in the
  /// environment where the Dart Tooling Daemon is running.
  ///
  /// If [uri] is not a directory, throws an [RpcException] exception with error
  /// code [RpcErrorCodes.kDirectoryDoesNotExist].
  ///
  /// If [uri] is not contained in the IDE workspace roots, then an
  /// [RpcException] with [RpcErrorCodes.kPermissionDenied] is thrown.
  ///
  /// If [uri] does not have a file scheme, then an [RpcException] with
  /// [RpcErrorCodes.kExpectsUriParamWithFileScheme] is thrown.
  ///
  /// The returned uris will be `file://` Uris.
  Future<UriList> listDirectoryContents(Uri uri) async {
    final result = await call(
      FileSystemServiceConstants.serviceName,
      FileSystemServiceConstants.listDirectoryContents,
      params: {
        DtdParameters.uri: uri.toString(),
      },
    );
    return UriList.fromDTDResponse(result);
  }

  /// Sets the IDE workspace roots for the FileSystem service.
  ///
  /// This is a privileged RPC that require's a [secret], which is provided by
  /// the Dart Tooling Daemon, to be called successfully. This secret is
  /// generated by the daemon and provided to its spawner to ensure only trusted
  /// clients can set workspace roots. If [secret] is invalid, an [RpcException]
  /// with error code [RpcErrorCodes.kPermissionDenied] is thrown.
  ///
  /// If [secret] does not match the secret created when Dart Tooling Daemon was
  /// created, then an [RpcException] with [RpcErrorCodes.kPermissionDenied] is
  /// thrown.
  ///
  /// If one of the [roots] is missing a "file" scheme then an [RpcException]
  /// with [RpcErrorCodes.kExpectsUriParamWithFileScheme] is thrown.
  Future<void> setIDEWorkspaceRoots(String secret, List<Uri> roots) async {
    await call(
      FileSystemServiceConstants.serviceName,
      FileSystemServiceConstants.setIDEWorkspaceRoots,
      params: {
        DtdParameters.roots: roots.map<String>((e) => e.toString()).toList(),
        DtdParameters.secret: secret,
      },
    );
  }

  /// Gets the IDE workspace roots for the FileSystem service.
  ///
  /// The returned uris will be `file://` Uris.
  Future<IDEWorkspaceRoots> getIDEWorkspaceRoots() async {
    final result = await call(
      FileSystemServiceConstants.serviceName,
      FileSystemServiceConstants.getIDEWorkspaceRoots,
    );
    return IDEWorkspaceRoots.fromDTDResponse(result);
  }

  /// Gets the project roots contained within the current set of IDE workspace
  /// roots.
  ///
  /// A project root is any directory that contains a 'pubspec.yaml' file. If
  /// IDE workspace roots are not set, or if there are no project roots within
  /// the IDE workspace roots, this method will return an empty [UriList].
  ///
  /// [depth] is the maximum depth that each IDE workspace root directory tree
  /// will be searched for project roots.
  ///
  /// The returned uris will be `file://` Uris.
  Future<UriList> getProjectRoots({
    int depth = defaultGetProjectRootsDepth,
  }) async {
    final result = await call(
      FileSystemServiceConstants.serviceName,
      FileSystemServiceConstants.getProjectRoots,
      params: {DtdParameters.depth: depth},
    );
    return UriList.fromDTDResponse(result);
  }
}
