// Utility Types and Functions
type NestedPath = string[];

class ParseError extends Error {
    public type: string = "ParseError";
    public path: NestedPath; // Changed from 'trace' to 'path' for clarity
    public input: any;

    constructor(input: any, path: NestedPath, msg: string) {
        super(msg);
        this.input = input;
        this.path = path;
    }

    // Method to prepend a new path segment
    prependPath(segment: string): ParseError {
        return new ParseError(
            this.input,
            [segment, ...this.path],
            this.message,
        );
    }

    // Method to append a new path segment
    appendPath(segment: string): ParseError {
        return new ParseError(
            this.input,
            [...this.path, segment],
            this.message,
        );
    }

    // Format the error message with the path
    formattedMessage(): string {
        return `Error at "${this.path.join(".") || "root"}": ${this.message}`;
    }
}

function createParseError(
    input: any,
    path: NestedPath,
    msg: string,
): ParseError {
    return new ParseError(input, path, msg);
}

// Base Schema Classes
export interface Schema<T> {
    parse(input: unknown, path?: NestedPath): Result<T, ParseError>;
    checkIfValid(input: unknown): boolean;
    nullable(): NullableSchema<Schema<T>>;
    option(): OptionSchema<Schema<T>>;
    or<S extends Schema<any>[]>(...schema: S): UnionSchema<[this, ...S]>;
}

type CheckFunction<T> = (input: T, path: NestedPath) => ParseError | void;

export abstract class BaseSchema<T> implements Schema<T> {
    protected checks: CheckFunction<T>[] = [];

    public addCheck(check: CheckFunction<T>): this {
        this.checks.push(check);
        return this;
    }

    protected runChecks(input: T, path: NestedPath): Result<T, ParseError> {
        for (const check of this.checks) {
            const error = check(input, path);
            if (error) {
                return err(error);
            }
        }
        return ok(input);
    }

    checkIfValid(input: unknown): boolean {
        return this.parse(input).isOk();
    }

    nullable(): NullableSchema<Schema<T>> {
        return new NullableSchema(this);
    }

    or<S extends Schema<any>[]>(...schema: S): UnionSchema<[this, ...S]> {
        return new UnionSchema(this, ...schema);
    }

    option(): OptionSchema<Schema<T>> {
        return new OptionSchema(this);
    }

    abstract parse(input: unknown, path?: NestedPath): Result<T, ParseError>;
}

export abstract class PrimitiveSchema<T> extends BaseSchema<T> {
    protected abstract initialCheck(
        input: unknown,
        path: NestedPath,
    ): Result<T, ParseError>;

    protected checkPrimitive<U = T>(
        input: unknown,
        type:
            | "string"
            | "number"
            | "bigint"
            | "boolean"
            | "symbol"
            | "undefined"
            | "object"
            | "function",
        path: NestedPath,
    ): Result<U, ParseError> {
        const inputType = typeof input;

        if (inputType === type) {
            return ok(input as U);
        }
        return err(
            createParseError(
                input,
                path,
                `Expected type '${type}', received '${inputType}'`,
            ),
        );
    }

    public parse(input: unknown, path: NestedPath = []): Result<T, ParseError> {
        return this.initialCheck(input, path).andThen((value) =>
            this.runChecks(value, path)
        );
    }
}

// Example: StringSchema with Improved Error Handling
export class StringSchema extends PrimitiveSchema<string> {
    private static readonly emailRegex =
        /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;

    private static readonly ipRegex =
        /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(?!$)|$){4}$/;

    protected initialCheck(
        input: unknown,
        path: NestedPath,
    ): Result<string, ParseError> {
        return this.checkPrimitive(input, "string", path);
    }

    public max(length: number, msg?: string): this {
        return this.addCheck((input, path) => {
            if (input.length <= length) return;
            return createParseError(
                input,
                path,
                msg ||
                    `String length must be at most ${length} characters long`,
            );
        });
    }

    public min(length: number, msg?: string): this {
        return this.addCheck((input, path) => {
            if (input.length >= length) return;
            return createParseError(
                input,
                path,
                msg ||
                    `String length must be at least ${length} characters long`,
            );
        });
    }

    public regex(pattern: RegExp, msg?: string): this {
        return this.addCheck((input, path) => {
            if (pattern.test(input)) return;
            return createParseError(
                input,
                path,
                msg || `String does not match the pattern ${pattern}`,
            );
        });
    }

    public email(msg?: string): this {
        return this.regex(
            StringSchema.emailRegex,
            msg || "Invalid email address",
        );
    }

    public ip(msg?: string): this {
        return this.regex(StringSchema.ipRegex, msg || "Invalid IP address");
    }
}

// Refactored ObjectSchema with Improved Error Handling
class ObjectSchema<O extends Record<string, Schema<any>>>
    extends BaseSchema<{ [K in keyof O]: InferSchema<O[K]> }> {
    constructor(private readonly schema: O) {
        super();
    }

    protected initialCheck(
        input: unknown,
        path: NestedPath,
    ): Result<{ [K in keyof O]: InferSchema<O[K]> }, ParseError> {
        if (typeof input !== "object" || input === null) {
            return err(
                createParseError(
                    input,
                    path,
                    `Expected an object, received '${typeof input}'`,
                ),
            );
        }

        const obj = input as Record<string, any>;
        const parsedObj: Partial<{ [K in keyof O]: InferSchema<O[K]> }> = {};

        for (const key in this.schema) {
            const value = obj[key];
            const fieldPath = [...path, key];
            const result = this.schema[key].parse(value, fieldPath);

            if (result.isErr()) {
                return err(result.error);
            }

            parsedObj[key] = result.value;
        }

        return ok(parsedObj as { [K in keyof O]: InferSchema<O[K]> });
    }
}

// Refactored UnionSchema with Simplified Error Handling
class UnionSchema<S extends Schema<any>[]>
    extends BaseSchema<InferSchemaUnion<S>> {
    constructor(...schemas: S) {
        super();
        this.schemas = schemas;
    }

    private schemas: S;

    protected initialCheck(
        input: unknown,
        path: NestedPath,
    ): Result<InferSchemaUnion<S>, ParseError> {
        const errors: ParseError[] = [];

        for (const schema of this.schemas) {
            const result = schema.parse(input, path);
            if (result.isOk()) {
                return ok(result.value);
            }
            errors.push(result.error);
        }

        // Combine error messages for better readability
        const combinedMessage = errors.map((err) => err.formattedMessage())
            .join(" | ");
        return err(
            createParseError(
                input,
                path,
                `Union validation failed: ${combinedMessage}`,
            ),
        );
    }
}

// Refactored ArraySchema with Improved Error Handling
class ArraySchema<S extends Schema<any>> extends BaseSchema<InferSchema<S>[]> {
    constructor(private readonly schema: S) {
        super();
    }

    protected initialCheck(
        input: unknown,
        path: NestedPath,
    ): Result<InferSchema<S>[], ParseError> {
        if (!Array.isArray(input)) {
            return err(
                createParseError(
                    input,
                    path,
                    `Expected an array, received '${typeof input}'`,
                ),
            );
        }

        const parsedArray: InferSchema<S>[] = [];

        for (let i = 0; i < input.length; i++) {
            const elementPath = [...path, `${i}`];
            const result = this.schema.parse(input[i], elementPath);
            if (result.isErr()) {
                return err(result.error);
            }
            parsedArray.push(result.value);
        }

        return ok(parsedArray);
    }
}

// NullableSchema, OptionSchema, and Other Schemas would follow similar patterns,
// ensuring that error paths are correctly managed and messages are clear.

// Example Validator Usage
class Validator {
    string(): StringSchema {
        return new StringSchema();
    }

    // ... other schema methods remain unchanged ...

    union<S extends Schema<any>[]>(...schemas: S): UnionSchema<S> {
        return new UnionSchema(...schemas);
    }

    array<S extends Schema<any>>(elementSchema: S): ArraySchema<S> {
        return new ArraySchema(elementSchema);
    }

    // ... other schema methods remain unchanged ...
}

const v = new Validator();

// Example Usage with Improved Error Handling
const schema = v.union(
    v.string().max(4, "String exceeds maximum length of 4"),
    v.string().min(10, "Number must be at least 10"),
);

const res = schema.parse("11234", ["input"]);

if (res.isErr()) {
    console.error(res.error.formattedMessage());
} else {
    console.log("Parsed Value:", res.value);
}

// Utility Types
export type InferSchema<S> = S extends Schema<infer T> ? T : never;
type InferSchemaUnion<S extends Schema<any>[]> = S[number] extends
    Schema<infer U> ? U : never;
type NestedArray<T> = T | NestedArray<T>[];
