1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import type { SourceFile } from 'typescript';
17import { SyntaxKind } from 'typescript';
18import type { MethodEntity } from '../declaration-node/methodDeclaration';
19import type { FunctionEntity } from '../declaration-node/functionDeclaration';
20import type { MethodSignatureEntity } from '../declaration-node/methodSignatureDeclaration';
21import {
22  generateSymbolIterator,
23  getCallbackStatement,
24  getReturnStatement,
25  getWarnConsole,
26  getReturnData,
27  getOverloadedFunctionCallbackStatement,
28  overloadedFunctionArr
29} from './generateCommonUtil';
30
31interface MethodArrayProps {
32  methodArray: Array<MethodEntity>;
33  methodEntity: MethodEntity;
34  sourceFile: SourceFile;
35  mockApi: string;
36  methodBody: string;
37}
38
39interface MethodArrayItemForEachProps {
40  returnSet: Set<string>;
41  value: MethodEntity | FunctionEntity | MethodSignatureEntity;
42  argSet: Set<string>;
43  isCallBack: boolean;
44  argParamsSet: string;
45  needOverloaded: boolean;
46}
47
48interface MethodArrayBack {
49  returnSet: Set<string>;
50  methodBody: string;
51  isCallBack: boolean;
52}
53
54/**
55 * generate class method
56 * @param rootName
57 * @param methodArray
58 * @param sourceFile
59 * @returns
60 */
61export function generateCommonMethod(
62  rootName: string,
63  methodArray: Array<MethodEntity>,
64  sourceFile: SourceFile,
65  mockApi: string
66): string {
67  let methodBody = '';
68  const methodEntity = methodArray[0];
69  if (methodEntity.functionName.name === 'Symbol.iterator') {
70    methodBody += `this[${methodEntity.functionName.name}] = function(...args) {`;
71    methodBody += getWarnConsole(rootName, methodEntity.functionName.name);
72    methodBody += generateSymbolIterator(methodEntity);
73    methodBody += '};\n';
74    return methodBody;
75  } else {
76    methodBody += `this.${methodEntity.functionName.name} = function(...args) {`;
77    methodBody += getWarnConsole(rootName, methodEntity.functionName.name);
78  }
79
80  if (methodArray.length === 1) {
81    const args = methodEntity.args;
82    const len = args.length;
83    if (len && args[len - 1].paramName.toLowerCase().includes('callback')) {
84      methodBody += getCallbackStatement(mockApi, args[len - 1]?.paramTypeString);
85    }
86    if (methodEntity.returnType.returnKind !== SyntaxKind.VoidKeyword) {
87      if (methodEntity.functionName.name === 'getApplicationContext') {
88        methodBody += getApplicationContextValue(methodEntity.functionName.name);
89      } else {
90        methodBody += getReturnStatement(methodEntity.returnType, sourceFile);
91      }
92    }
93  } else {
94    const methodArrayBack = methodArrayForEach({ methodArray, methodEntity, sourceFile, mockApi, methodBody });
95    methodBody = returnSetForEach(methodArrayBack, methodArray, sourceFile, mockApi);
96  }
97  methodBody += '};\n';
98  return methodBody;
99}
100
101function getApplicationContextValue(name: string): string {
102  return `
103const mockData = {
104  on: function (...args) {
105    console.warn(
106      'The ${name}.on interface in the Previewer is a mocked implementation and may behave differently than on a real device.'
107    );
108    if (args && ['environment'].includes(args[0])) {
109      if (args && typeof args[args.length - 1] === 'function') {
110        const EnvironmentCallback = mockEnvironmentCallback();
111        args[args.length - 1].call(this, new EnvironmentCallback());
112      }
113    }
114    return 0;
115  },
116  off: function (...args) {
117    console.warn(
118      'The ${name}.off interface in the Previewer is a mocked implementation and may behave differently than on a real device.'
119    );
120    if (args && ['environment'].includes(args[0])) {
121      if (args && typeof args[args.length - 1] === 'function') {
122        args[args.length - 1].call(
123          this,
124          { 'code': '', 'data': '', 'name': '', 'message': '', 'stack': '' },
125          '[PC Preview] unknown type'
126        );
127      }
128    } 
129    return new Promise((resolve, reject) => {
130        resolve('[PC Preview] unknown type');
131    });
132  }
133};
134return mockData;
135  `;
136}
137
138/**
139 * method Array ForEach
140 * @param props
141 * @returns
142 */
143function methodArrayForEach(props: MethodArrayProps): MethodArrayBack {
144  let argSet: Set<string> = new Set<string>();
145  let argParamsSet: string = '';
146  let returnSet: Set<string> = new Set<string>();
147  let isCallBack = false;
148  let needOverloaded = false;
149  props.methodArray.forEach(value => {
150    ({ returnSet, argSet, isCallBack, argParamsSet, needOverloaded} =
151      methodArrayItemForEach({returnSet, value, argSet, isCallBack, argParamsSet, needOverloaded}));
152  });
153  if (isCallBack) {
154    if (overloadedFunctionArr.includes(props.methodEntity.functionName.name) && needOverloaded) {
155      props.methodBody += getOverloadedFunctionCallbackStatement(props.methodArray, props.sourceFile, props.mockApi);
156    } else {
157      props.methodBody += getCallbackStatement(props.mockApi, argParamsSet);
158    }
159  }
160  return {
161    returnSet,
162    methodBody: props.methodBody,
163    isCallBack
164  };
165}
166
167/**
168 * method ArrayItem ForEach
169 * @param props
170 * @returns
171 */
172export function methodArrayItemForEach(props: MethodArrayItemForEachProps): MethodArrayItemForEachProps {
173  props.returnSet.add(props.value.returnType.returnKindName);
174  props.value.args.forEach(arg => {
175    props.argSet.add(arg.paramName);
176    if (arg.paramName.toLowerCase().includes('callback')) {
177      props.isCallBack = true;
178      if (arg.paramTypeString) {
179        props.argParamsSet = arg.paramTypeString;
180      }
181    }
182    if (
183      arg.paramTypeString.startsWith("'") && arg.paramTypeString.endsWith("'") ||
184      arg.paramTypeString.startsWith('"') && arg.paramTypeString.endsWith('"')
185    ) {
186      props.needOverloaded = true;
187    }
188  });
189  return props;
190}
191
192/**
193 * returnSet ForEach
194 * @param props
195 * @param methodArray
196 * @param sourceFile
197 * @param mockApi
198 * @returns
199 */
200function returnSetForEach(
201  props: MethodArrayBack,
202  methodArray: Array<MethodEntity>,
203  sourceFile: SourceFile,
204  mockApi: string
205): string {
206  let isReturnPromise = false;
207  let promiseReturnValue = '';
208  let methodOtherReturnValue = '';
209  props.returnSet.forEach(value => {
210    if (value.includes('Promise<')) {
211      isReturnPromise = true;
212      promiseReturnValue = value;
213    } else {
214      if (!methodOtherReturnValue) {
215        methodOtherReturnValue = value;
216      }
217    }
218  });
219  if (isReturnPromise) {
220    if (promiseReturnValue) {
221      let returnType = null;
222      methodArray.forEach(value => {
223        if (value.returnType.returnKindName === promiseReturnValue) {
224          returnType = value.returnType;
225        }
226      });
227      props.methodBody += getReturnData(props.isCallBack, isReturnPromise, returnType, sourceFile, mockApi);
228    } else {
229      props.methodBody += `
230            return new Promise((resolve, reject) => {
231              resolve('[PC Preview] unknow boolean');
232            })
233          `;
234    }
235  } else if (methodOtherReturnValue) {
236    let returnType = null;
237    methodArray.forEach(value => {
238      if (value.returnType.returnKindName === methodOtherReturnValue) {
239        returnType = value.returnType;
240      }
241    });
242    props.methodBody += getReturnData(props.isCallBack, isReturnPromise, returnType, sourceFile, mockApi);
243  }
244  return props.methodBody;
245}
246