import { Readable } from "node:stream";
import { CorsOptions } from "cors";
import { Buffer } from "node:buffer";
import Cookies from "cookies";
import { Options } from "co-body";
import formidable from "formidable";
import { WebSocketServer } from "ws";
import http from "node:http";
import { Connect } from "vite";

//#region src/types.d.ts

/**
* Configure plugin
*
* 插件配置项
*/
interface MockServerPluginOptions {
  /**
  * To configure the path matching rules for http mock services,
  * any request path starting with prefix will be intercepted and proxied.
  * If the prefix starts with `^`, it will be recognized as a `RegExp`.
  *
  * 为 http mock 服务配置 路径匹配规则，任何请求路径以 prefix 开头的都将被拦截代理。
  * 如果 prefix 以 `^` 开头，将被识别为 `RegExp`。
  * @default []
  * @example ['^/api']
  */
  prefix?: string | string[];
  /**
  * Configure path matching rules for WebSocket mock service.
  * Any ws/wss requests with a request path starting with wsPrefix
  * will be intercepted by the proxy.
  * If wsPrefix starts with `^`, it will be recognized as a `RegExp`.
  *
  * 为 websocket mock 服务配置 路径匹配规则， 任何请求路径以 wsPrefix 开头的 ws/wss请求，
  * 都将被代理拦截。
  * 如果 wsPrefix 以 `^` 开头，将被识别为 `RegExp`。
  * @default []
  * @example ['/socket.io']
  */
  wsPrefix?: string | string[];
  /**
  * Configure the matching context for `include` and `exclude`.
  *
  * 配置 `include` 和 `exclude` 的匹配上下文
  *
  * @default process.cwd()
  */
  cwd?: string;
  /**
  * glob string matching mock includes files
  *
  * glob 字符串匹配 mock 包含的文件
  * @see [picomatch](https://github.com/micromatch/picomatch#globbing-features)
  * @default []
  */
  include?: string | string[];
  /**
  * glob string matching mock excluded files
  *
  * glob 字符串匹配 mock 排除的文件
  * @see [picomatch](https://github.com/micromatch/picomatch#globbing-features)
  */
  exclude?: string | string[];
  /**
  * Enable log and configure log level
  *
  * 开启日志，或配置 日志级别
  * @default 'info'
  */
  log?: boolean | LogLevel;
  /**
  * When the mock resource is hot updated, only the data content is updated,
  * but the page is not refreshed by default.
  * If you want to refresh the page every time you modify a mock file,
  * you can open this option.
  *
  * mock资源热更新时，仅更新了数据内容，但是默认不重新刷新页面。
  * 当你希望每次修改mock文件都刷新页面时，可以打开此选项。
  * @default false
  */
  reload?: boolean;
  /**
  * Configure to `cors`
  *
  * 配置 `cors`
  * @default true
  * @see [cors](https://github.com/expressjs/cors#configuration-options)
  */
  cors?: boolean | CorsOptions;
  /**
  * formidable options
  * @see [formidable](https://github.com/node-formidable/formidable#options)
  */
  formidableOptions?: formidable.Options;
  /**
  * cookies options
  * @see [cookies](https://github.com/pillarjs/cookies#new-cookiesrequest-response--options)
  */
  cookiesOptions?: Cookies.Option;
  /**
  * Configure to `co-body`
  *
  * 配置 `co-body`
  *
  * @see [co-body](https://github.com/cojs/co-body#options)
  */
  bodyParserOptions?: BodyParserOptions;
  /**
  * When you need to build a small mock service, you can configure this option.
  *
  * 当需要构建一个小型mock服务时，可配置此项
  * @default false
  */
  build?: boolean | ServerBuildOption;
  /**
  * Priority sorting for path matching rules is valid only for rules containing dynamic parameters.
  * In most cases, the default sorting rules can meet the needs.
  * However, in some cases where custom sorting rules are required, this option can be used.
  *
  * 路径匹配规则优先级排序，仅对包含动态参数的规则有效。
  * 大部分情况下默认的排序规则都可以满足需求。
  * 但有些情况下，需要自定义排序规则时，可以使用此选项。
  *
  * @example
  * ```ts
  * export default {
  *   priority: {
  *     global: ['/api/:a/b/c', '/api/a/:b/c', '/api/a/b/:c'],
  *     special: {
  *       '/api/:a/:b/c': {
  *         rules: ['/api/a/:b/:c', '/api/a/b/:c'],
  *         when: ['/api/a/b/c']
  *        }
  *     }
  *   }
  * }
  * ```
  */
  priority?: MockMatchPriority;
}
interface MockMatchPriority {
  /**
  * The priority of matching rules is global.
  * The rules declared in this option will take priority over the default rules.
  * The higher the position of the rule in the array, the higher the priority.
  *
  * Do not declare general rules in this option, such as /api/(.*),
  * as it will prevent subsequent rules from taking effect.
  * Unless you are clear about the priority of the rules,
  * most of the time you do not need to configure this option.
  *
  * 匹配规则优先级, 全局生效。
  * 声明在该选项中的规则将优先于默认规则生效。
  * 规则在数组越靠前的位置，优先级越高。
  *
  * 不要在此选项中声明通用性的规则，比如 `/api/(.*)`，这将导致后续的规则无法生效。
  * 除非你明确知道规则的优先级，否则大多数情况下都不需要配置该选项。
  * @default []
  */
  global?: string[];
  /**
  * For some special cases where the priority of certain rules needs to be adjusted,
  * this option can be used. For example, when a request matches both Rule A and Rule B,
  * and Rule A has a higher priority than Rule B, but it is desired for Rule B to take effect.
  *
  * 对于一些特殊情况，需要调整部分规则的优先级，可以使用此选项。
  * 比如一个请求同时命中了规则 A 和 B，且 A 比 B 优先级高， 但期望规则 B 生效时。
  *
  * @example
  * ```ts
  * {
  *   special: {
  *     // /api/a/:b/c 优先级将提升到 /api/a/b/:c 前面
  *     // The /api/a/:b/c priority is promoted to /api/a/b/:c
  *     '/api/a/:b/c': ['/api/a/b/:c'],
  *     // 仅在请求满足 /api/a/b/c 时生效
  *     // Only when the request satisfies /api/a/b/c
  *     '/api/:a/b/c': {
  *        rules: ['/api/a/:b/c'],
  *        when: ['/api/a/b/c']
  *      }
  *   }
  * }
  * ```
  */
  special?: MockMatchSpecialPriority;
}
interface MockMatchSpecialPriority {
  /**
  * When both A and B or C match, and B or C is at the top of the sort order,
  * insert A into the top position.The `when` option is used to further constrain
  * the priority adjustment to be effective only for certain requests.
  *
  * 当 A 与 B或 C 同时满足匹配，`B` 或 `C` 在排序首位时，将A插入到首位。
  * when 选项用于进一步约束该优先级调整仅针对哪些请求有效。
  *
  * @example
  * ```ts
  * {
  *   A: ['B', 'C'],
  *   A: { rules: ['B', 'C'], when: ['/api/a/b/c'] }
  * }
  * ```
  */
  [key: string]: string[] | {
    rules: string[];
    when: string[];
  };
}
type BodyParserOptions = Options & {
  jsonLimit?: string | number;
  formLimit?: string | number;
  textLimit?: string | number;
};
interface ServerBuildOption {
  /**
  * Service startup port
  *
  * 服务启动端口
  * @default 8080
  */
  serverPort?: number;
  /**
  * Service application output directory
  *
  * 服务应用输出目录
  * @default 'dist/mockServer'
  */
  dist?: string;
  /**
  * Service application log level
  *
  * 服务应用日志级别
  * @default 'error'
  */
  log?: LogLevel;
}
type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "TRACE" | "OPTIONS";
type Headers = http.IncomingHttpHeaders;
type ResponseBody = Record<string, any> | any[] | string | number | Readable | Buffer | null;
/**
* 扩展 request，添加额外的属性和方法
*/
interface ExtraRequest {
  /**
  * The query string located after `?` in the request address has been parsed into JSON.
  *
  * 请求地址中位于 `?` 后面的 queryString，已解析为 json
  */
  query: Record<string, any>;
  /**
  * The queryString located after `?` in the referer request has been parsed as JSON.
  *
  * 请求 referer 中位于 `?` 后面的 queryString，已解析为 json
  */
  refererQuery: Record<string, any>;
  /**
  * Body data in the request
  *
  * 请求体中 body 数据
  */
  body: Record<string, any>;
  /**
  * The params parameter parsed from the `/api/id/:id` in the request address.
  *
  * 请求地址中，`/api/id/:id` 解析后的 params 参数
  */
  params: Record<string, any>;
  /**
  * headers data in the request
  * 请求体中 headers
  */
  headers: Headers;
  /**
  * Get the cookie carried in the request.
  *
  * 获取 请求中携带的 cookie
  * @see [cookies](https://github.com/pillarjs/cookies#cookiesgetname--options)
  */
  getCookie: (name: string, option?: Cookies.GetOption) => string | undefined;
}
type MockRequest = Connect.IncomingMessage & ExtraRequest;
type MockResponse = http.ServerResponse<http.IncomingMessage> & {
  /**
  * Set cookie in response
  *
  * 向请求响应中设置 cookie
  * @see [cookies](https://github.com/pillarjs/cookies#cookiessetname--values--options)
  */
  setCookie: (name: string, value?: string | null, option?: Cookies.SetOption) => void;
};
type ResponseBodyFn = (request: MockRequest) => ResponseBody | Promise<ResponseBody>;
type ResponseHeaderFn = (request: MockRequest) => Headers | Promise<Headers>;
type CookieValue = string | [string, Cookies.SetOption];
type ResponseCookies = Record<string, CookieValue>;
type ResponseCookiesFn = (request: MockRequest) => ResponseCookies | Promise<ResponseCookies>;
interface MockBaseItem {
  /**
  * The interface address that needs to be mocked,
  * supported by `path-to-regexp` for path matching.
  *
  * 需要进行 mock 的接口地址, 由 `path-to-regexp` 提供路径匹配支持
  * @see [path-to-regexp](https://github.com/pillarjs/path-to-regexp)
  * @example
  * ```txt
  * /api/login
  * /api/post/:id
  * /api/post/:id
  * /api/anything/(.*)
  * ```
  */
  url: string;
  /**
  * Enable WebSocket interface simulation
  *
  * 开启 websocket 接口模拟
  *
  * @default false
  */
  ws?: boolean;
  /**
  * Whether to enable mock for this interface.
  * In most scenarios, we only need to mock some interfaces instead of all requests that
  * have been configured with mock.
  * Therefore, it is important to be able to configure whether to enable it or not.
  *
  * 是否启动对该接口的mock，在多数场景下，我们仅需要对部分接口进行 mock，
  * 而不是对所有配置了mock的请求进行全量mock，所以是否能够配置是否启用很重要
  * @default true
  */
  enabled?: boolean;
  /**
  * Enable log and configure log level
  *
  * 开启日志，或配置 日志级别
  * @default 'info'
  */
  log?: boolean | LogLevel;
}
interface MockHttpItem extends MockBaseItem {
  /**
  * The interface allows request methods, and by default allows both GET and POST.
  *
  * 该接口允许的 请求方法，默认同时支持 GET 和 POST
  * @default ['POST','GET']
  */
  method?: Method | Method[];
  /**
  * Configure the response body headers
  *
  * 配置响应体 headers
  * @default
  * ```json
  * { "Content-Type": "application/json" }
  * ```
  */
  headers?: Headers | ResponseHeaderFn;
  /**
  * Configure Response Header Status Code
  *
  * 配置 响应头状态码
  * @default 200
  */
  status?: number;
  /**
  * Configure response header status text
  *
  * 配置响应头状态文本
  * @default 'OK'
  */
  statusText?: string;
  /**
  * Configure response delay time,
  * If an array is passed in, it represents the range of delay time.
  * unit: `ms`
  *
  * 配置响应延迟时间, 如果传入的是一个数组，则代表延迟时间的范围
  * 单位： `ms`
  * @default 0
  */
  delay?: number | [number, number];
  /**
  * Configure response body cookies
  *
  * 设置响应体 cookies
  * @example
  * ```ts
  * export default {
  *   cookies: {
  *     'token1': '1234567',
  *     'token2': ['1234567', { path: '/' }],
  *   },
  * }
  * ```
  * @example
  * ```ts
  * export default {
  *   cookies: function (request) {
  *     return {
  *       'token1': '1234567',
  *       'token2': ['1234567', { path: '/' }],
  *     }
  *   },
  * }
  * ```
  */
  cookies?: ResponseCookies | ResponseCookiesFn;
  /**
  * Response body data type, optional values include `text, json, buffer`.
  *
  * And also support types included in `mime-db`.
  * When the response body returns a file and you are not sure which type to use,
  * you can pass the file name as the value. The plugin will internally search for matching
  * `content-type` based on the file name suffix.
  *
  * However, if it is a TypeScript file such as `a.ts`, it may not be correctly matched
  * as a JavaScript script. You need to modify `a.ts` to `a.js` as the value passed
  * in order to recognize it correctly.
  *
  * 响应体数据类型, 可选值包括 `text, json, buffer`，
  *
  * 还支持`mime-db`中的包含的类型。
  * 当响应体返回的是一个文件，而你不确定应该使用哪个类型时，可以将文件名作为值传入，
  * 插件内部会根据文件名后缀查找匹配的`content-type`。
  *
  * 但如果是 `typescript`文件如 `a.ts`，可能不会被正确匹配为 `javascript`脚本，
  * 你需要将 `a.ts` 修改为 `a.js`作为值传入才能正确识别。
  * @see [mime-db](https://github.com/jshttp/mime-db)
  * @default 'json'
  * @example
  * ```txt
  * json
  * buffer
  * my-app.dmg
  * music.mp4
  * ```
  */
  type?: "text" | "json" | "buffer" | string;
  /**
  * Configure response body data content
  *
  * 配置响应体数据内容
  * @default ''
  * @example
  * ```ts
  * export default {
  *   body: { a: 1 },
  * }
  * ```
  * @example
  * ```ts
  * export default {
  *   body: function(request) {
  *     return { a: 1, query: request.query }
  *   },
  * }
  * ```
  */
  body?: ResponseBody | ResponseBodyFn;
  /**
  * If you need to set complex response content, you can use the response method,
  * which is a middleware. Here, you can get information such as req
  * and res of the http request,
  * and then return response data through res.write() | res.end().
  * Otherwise, you need to execute next() method.
  * In `req`, you can also get parsed request information such as
  * `query`, `params`, `body` and `refererQuery`.
  *
  * 如果需要设置复杂的响应内容，可以使用 response 方法，
  * 该方法是一个 middleware，你可以在这里拿到 http 请求的 req、res等信息，
  * 然后通过 res.write() | res.end() 返回响应数据， 否则需要执行 next() 方法。
  * 在 `req` 中，还可以拿到 query、params、body, refererQuery 等已解析的请求信息。
  *
  * @see [connect](https://github.com/senchalabs/connect#appusefn)
  * @example
  * ```ts
  * export default {
  *   response(req, res) {
  *     res.setHeader('Content-Type', 'application/json')
  *     res.end(JSON.stringify({ a: 1 }))
  *   },
  * }
  * ```
  *
  */
  response?: (req: MockRequest, res: MockResponse, next: Connect.NextFunction) => void | Promise<void>;
  /**
  * Request Validator
  *
  * Sometimes, for the same API request, data needs to be returned based
  * on different request parameters.
  * However, if all of this is written in a single mock's body or response,
  * the content can become cumbersome and difficult to manage.
  * The function of a validator allows you to configure multiple mocks with
  * the same URL simultaneously and determine which mock should be used through validation.
  *
  * 请求验证器
  *
  * 有时候，一个相同的API请求，需要根据不同的请求参数，来决定返回数据，
  * 但全部都在单个 mock中的 body或者 response 中写，内容会很庞杂，不好管理，
  * 验证器的功能，允许你同时配置多条相同url的mock，通过验证器来判断使哪个mock生效。
  * @example
  * ```ts
  * export default {
  *   validator: {
  *     query: { id: 123 }
  *   }
  * }
  * ```
  * @example
  * ```ts
  * export default {
  *   validator: function(request) {
  *     return request.query.id === 123
  *   }
  * }
  * ```
  */
  validator?: Partial<Omit<ExtraRequest, "getCookie">> | ((request: ExtraRequest) => boolean);
  ws?: false;
}
interface MockWebsocketItem extends MockBaseItem {
  ws: true;
  /**
  * Configure Websocket Server
  *
  * 配置 Websocket Server
  * @example
  * ```ts
  * export default {
  *   ws: true
  *   setup: (wss, { onCleanup }) => {
  *     wss.on('connection', (ws,req) => {
  *       ws.on('message', (raw) => console.log(raw))
  *       const timer = setInterval(
  *         () => ws.send(JSON.stringify({ type: 'connected' })),
  *         1000,
  *       )
  *       onCleanup(() => clearInterval(timer))
  *     })
  *     wss.on('error', (error) => console.error(error))
  *   }
  * }
  * ```
  */
  setup: (wss: WebSocketServer, context: WebSocketSetupContext) => void;
}
interface WebSocketSetupContext {
  /**
  * When defining WSS, you may perform some automatic or looping tasks.
  * However, when hot updating, the plugin will re-execute `setup()`,
  * which may result in duplicate registration of listening events and looping tasks
  * such as setTimeout. You can use `onCleanup()` to clear these automatic or looping tasks.
  *
  * 当你在定义 WSS 时，可能会执行一些自动任务或循环任务，
  * 但是当热更新时，插件内部会重新执行 setup() ，
  * 这可能导致出现 重复注册监听事件 和 循环任务如 `setTimeout` 等。
  * 通过 `onCleanup()` 可以来清除这些自动任务或循环任务。
  * @example
  * ``` ts
  * onCleanup(() => clearTimeout(timeId))
  * ```
  */
  onCleanup: (cleanup: () => void) => void;
}
type MockOptions = (MockHttpItem | MockWebsocketItem)[];
type FormidableFile = formidable.File | formidable.File[];
type LogType = "info" | "warn" | "error" | "debug";
type LogLevel = LogType | "silent";
//#endregion
export { FormidableFile, LogLevel, MockHttpItem, MockOptions, MockRequest, MockServerPluginOptions, MockWebsocketItem, ServerBuildOption };