# frozen_string_literal: true

module Script
  module Layers
    module Infrastructure
      module Languages
        class AssemblyScriptTaskRunner < TaskRunner
          NODE_MIN_VERSION = "14.15.0"
          NPM_MIN_VERSION = "5.2.0"

          BYTECODE_FILE = "build/script.wasm"
          METADATA_FILE = "build/metadata.json"
          SCRIPT_SDK_BUILD = "npm run build"
          NPM_SET_REGISTRY_COMMAND = "npm --userconfig ./.npmrc config set @shopify:registry https://registry.npmjs.com"
          NPM_SET_ENGINE_STRICT_COMMAND = "npm --userconfig ./.npmrc config set engine-strict true"
          NPM_INSTALL_COMMAND = "npm install --no-audit --no-optional --legacy-peer-deps --loglevel error"

          def build
            compile
            bytecode
          end

          def install_dependencies
            run_cmd_with_env_check(NPM_INSTALL_COMMAND)

          rescue Errors::SystemCallFailureError => e
            raise Errors::DependencyInstallError, e.out
          end

          def dependencies_installed?
            # Assuming if node_modules folder exist at root of script folder, all deps are installed
            ctx.dir_exist?("node_modules")
          end

          def metadata_file_location
            METADATA_FILE
          end

          def library_version(library_name)
            output = JSON.parse(run_cmd_with_env_check("npm -s list --json"))
            library_version_from_npm_list(output, library_name)
          rescue Errors::SystemCallFailureError => error
            library_version_from_npm_list_error_output(error, library_name)
          end

          def set_npm_config
            run_cmd_with_env_check(NPM_SET_REGISTRY_COMMAND)
            run_cmd_with_env_check(NPM_SET_ENGINE_STRICT_COMMAND)
          end

          private

          def ensure_environment
            return if defined?(@environment_checked)
            @environment_checked = true

            ToolVersionChecker.check_node(minimum_version: NODE_MIN_VERSION)
            ToolVersionChecker.check_npm(minimum_version: NPM_MIN_VERSION)
          end

          def run_cmd_with_env_check(cmd)
            ensure_environment
            CommandRunner.new(ctx: ctx).call(cmd)
          end

          def library_version_from_npm_list_error_output(error, library_name)
            # npm list can return a failure status code, even when returning the correct data.
            # This causes the CommandRunner to throw a SystemCallFailure error that contains the data.
            # In here, we check that the output contains `npm list`'s structure and extract the version.
            output = JSON.parse(error.out)
            raise error unless output.key?("dependencies")

            library_version_from_npm_list(output, library_name)
          rescue JSON::ParserError
            raise error
          end

          def library_version_from_npm_list(output, library_name)
            output.dig("dependencies", library_name, "version").tap do |version|
              raise Errors::APILibraryNotFoundError, library_name unless version
            end
          end

          def compile
            check_compilation_dependencies!
            run_cmd_with_env_check(SCRIPT_SDK_BUILD)
          end

          def check_compilation_dependencies!
            pkg = JSON.parse(File.read("package.json"))
            build_script = pkg.dig("scripts", "build")

            raise Errors::BuildScriptNotFoundError,
              "Build script not found" if build_script.nil?
          end

          def bytecode
            raise Errors::WebAssemblyBinaryNotFoundError unless ctx.file_exist?(BYTECODE_FILE)

            contents = ctx.binread(BYTECODE_FILE)
            ctx.rm(BYTECODE_FILE)

            contents
          end
        end
      end
    end
  end
end