/** Utilities shared by typed `.wrn` parsing, validation, and code generation. */

export type RuntimeType =
  "string" | "number" | "boolean" | "bigint" | "array" | "object" | "function" | "unknown";

export function runtimeTypeOf(annotation: string | undefined): RuntimeType {
  if (!annotation) return "unknown";
  const type = annotation.trim().replace(/^readonly\s+/, "");
  const unionParts = type.split("|").map((part) => part.trim());
  const concreteParts = unionParts.filter((part) => !/^(?:null|undefined)$/.test(part));
  if (
    /^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(type) ||
    (concreteParts.length > 0 && concreteParts.every((part) => /^(?:"[^"]*"|'[^']*')$/.test(part)))
  )
    return "string";
  if (
    /^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type) ||
    (concreteParts.length > 0 &&
      concreteParts.every((part) => /^-?(?:\d+\.?\d*|\.\d+)$/.test(part)))
  )
    return "number";
  if (
    /^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type) ||
    (concreteParts.length > 0 && concreteParts.every((part) => /^(?:true|false)$/.test(part)))
  )
    return "boolean";
  if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type)) return "bigint";
  if (/^(?:Array\s*<|ReadonlyArray\s*<|.+\[\])/.test(type) || /^\[/.test(type)) return "array";
  if (/^(?:Record\s*<|object\b|\{)/.test(type)) return "object";
  if (/=>|^(?:Function|\([^)]*\)\s*=>)/.test(type)) return "function";
  return "unknown";
}

export function inferredRuntimeType(expression: string): RuntimeType {
  const value = expression.trim();
  if (/^["'`]/.test(value)) return "string";
  if (/^-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(value)) return "number";
  if (/^(?:true|false)$/.test(value)) return "boolean";
  if (/^-?\d+n$/.test(value)) return "bigint";
  if (value.startsWith("[")) return "array";
  if (value.startsWith("{") || /^new\s+(?:Map|Set|Date)\b/.test(value)) return "object";
  if (/^(?:async\s+)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(value)) {
    return "function";
  }
  return "unknown";
}

export function validateTypedInitializer(
  name: string,
  annotation: string | undefined,
  expression: string,
): string | null {
  if (!annotation || expression.trim() === "undefined" || expression.trim() === "null") return null;
  const expected = runtimeTypeOf(annotation);
  const actual = inferredRuntimeType(expression);
  if (expected === "unknown" || actual === "unknown" || expected === actual) return null;
  return `${name} is declared as ${annotation}, but its initializer is ${actual}`;
}

/**
 * Browser behavior is evaluated as JavaScript, so erase TypeScript annotations
 * from ordinary function declarations before serializing it into HTML.
 * Server output retains the original typed source.
 */
export function eraseFunctionTypes(source: string): string {
  return source.replace(
    /(\b(?:async\s+)?function\s+[A-Za-z_$][\w$]*\s*\()([^)]*)(\)\s*)(?::\s*([^{}=>]+)\s*)?(\{)/g,
    (_whole, open: string, params: string, close: string, _returnType: string, brace: string) => {
      const plainParams = params
        .split(",")
        .map((param) =>
          param.replace(/([A-Za-z_$][\w$]*)(\?)?\s*:\s*([^=]+?)(?=\s*=|$)/, "$1").trim(),
        )
        .join(", ");
      return `${open}${plainParams}${close}${brace}`;
    },
  );
}
