type Primitive = string | number | boolean;
type PatternToType<P> =
  P extends NumberConstructor ? number :
    P extends StringConstructor ? string :
      P extends BooleanConstructor ? boolean :
        P extends RegExp ? string :
          unknown;
interface FieldSchema<Key extends string = string, T = Primitive> {
  key: Key;
  /**
   * 校验规则
   */
  pattern?: NumberConstructor | StringConstructor | BooleanConstructor | RegExp;
  /**
   * 自定义解析函数
   */
  parse?: (raw: string) => T;
  /**
   * 默认值
   */
  default?: T;
  /**
   * 是否允许重复字段
   */
  multiple?: boolean;
  /** 是否必填（无 default 时使用） */
  required?: boolean;
}

type InferField<S extends FieldSchema> = S['multiple'] extends true ? PatternToType<S['pattern']>[] : PatternToType<S['pattern']>;


type IsRequired<S extends FieldSchema> = S['default'] extends undefined
  ? (S['required'] extends true ? true : false)
  : true;

export type InferResult<T extends readonly FieldSchema[]> =
  & { [S in T[number] as IsRequired<S> extends true ? S['key'] : never]: InferField<S>; }
  & { [S in T[number] as IsRequired<S> extends true ? never : S['key']]?: InferField<S>; };


export interface AnalysisOptions {
  /**
   * 严格模式
   */
  strict?: boolean;
}

/**
 * Streaming normalize
 * @description 支持
 *  - 标准 URL (a=1&b=2)
 *  - DSL 语法 (a:1;b:2)
 *  - pipe 语法 (el:a|b|c → el=a&el=b&el=c)
 *  - JSON 自动识别 (meta={"x":1,"y":[1,2]})
 * @param input
 * @return 标准 URL
 */
function normalizeInput(input?: string): string {
  if (!input) return '';
  // 去掉前导 ?
  const source = input.startsWith('?') ? input.slice(1) : input;

  const result: string[] = [];
  let key = '';
  let value = '';
  let readingKey = true;
  let inJSON = false;
  let jsonDepth = 0;

  const pushPair = () => {
    if (!key) return;
    if (value.includes('|') && !inJSON) value.split('|').forEach((value) => result.push(`${key}=${value}`));
    else result.push(`${key}=${value}`);
    key = '';
    value = '';
    readingKey = true;
  };

  const jsonStart = ['{', '['];
  const jsonEnd = ['}', ']'];

  for (const char of source) {
    // JSON 开始
    if (!inJSON && jsonStart.includes(char)) {
      inJSON = true;
      jsonDepth = 1;
      value += char;
      continue;
    }
    // JSON 内部
    if (inJSON) {
      value += char;
      if (jsonStart.includes(char)) jsonDepth++;
      if (jsonEnd.includes(char)) jsonDepth--;
      if (jsonDepth === 0) inJSON = false;
      continue;
    }
    if (['&',';'].includes(char) && !inJSON) {
      pushPair();
      continue;
    }
    if ([':', '='].includes(char) && readingKey) {
      readingKey = false;
      continue;
    }
    if (readingKey) key += char;
    else value += char;
  }

  pushPair();
  return result.join('&');
}

/**
 * 自动解析 JSON
 */
function autoParse(raw: string): any {
  const trimmed = raw.trim();
  if (
    (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
    (trimmed.startsWith('[') && trimmed.endsWith(']'))
  ) try { return JSON.parse(trimmed); } catch { return raw; }
  return raw;
}

/**
 * 自动解析并转换 pattern 类型
 */
function parseValue(raw: string, pattern?: FieldSchema['pattern'], parseFn?: FieldSchema['parse'], strict?: boolean): any {
  let value = parseFn ? parseFn(raw) : autoParse(raw);

  // 原始类型自动转换
  if (pattern === String) value = String(value);
  else if (pattern === Number) {
    value = Number(value);
    if (isNaN(value) && strict) throw new Error(`Invalid number: ${raw}`);
  } else if (pattern === Boolean) {
    if (value === 'true' || value === '1' || value === true) value = true;
    else if (value === 'false' || value === '0' || value === false) value = false;
    else if (strict) throw new Error(`Invalid boolean: ${raw}`);
    else value = Boolean(value);
  } else if (pattern instanceof RegExp && !pattern.test(String(value)) && strict) throw new Error(`Invalid value: ${raw}`);

  return value;
}

/**
 * 参数解析器
 * @description
 *  Raw Input
 *    ↓ normalizeInput
 *    ↓ URLSearchParams
 *    ↓ parseValue (JSON + JS 类型 + pattern)
 *    ↓ 类型安全输出
 * @example
 * ```
 * // 标准 URL
 * preset=1&el=debug&el=btn
 * // DSL 语法
 * preset:1;el:debug
 * // Pipe 语法
 * el:a|b|c
 * ```
 *
 * @param schema
 * @param value
 * @param options
 */
export function analysis<T extends readonly FieldSchema[]>(schema: T, value?: string, options?: AnalysisOptions): InferResult<T> {
  const input = normalizeInput(value);
  const params = new URLSearchParams(input);
  const result: Record<string, unknown> = {};

  for (const { key, default: defaultValue, multiple, required, pattern, parse } of schema) {
    const matches = params.getAll(key);
    if (matches.length === 0) {
      if (defaultValue != null) result[key] = defaultValue;
      else if (required && options?.strict) throw new Error(`Missing required field: ${key}`);
      continue;
    }
    const values = matches.map(raw => parseValue(raw, pattern, parse, options?.strict));
    result[key] = multiple ? values : values[0];
  }

  return result as InferResult<T>;
}
