import { randomUUID } from './random-uuid';
import { wrapStack } from './wrap-stack';

export interface GhostErrorOptions {
    /** Human-readable summary of what went wrong. */
    message?: string;
    /** HTTP status code associated with the error. */
    statusCode?: number;
    /** Logging severity for the error. */
    level?: string;
    /** Unique error identifier. Defaults to a random UUID. */
    id?: string;
    /** Human-readable context/help text. Do not use this for structured data. */
    context?: string;
    /** Human-readable remediation guidance. */
    help?: string;
    /** Ghost error class/type name, normally provided by concrete error classes. */
    errorType?: string;
    /** Structured metadata for logs, API responses, and Sentry extra data. */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    errorDetails?: any;
    /** Machine-readable UPPER_SNAKE_CASE error code for programmatic handling. */
    code?: string;
    /** Field/property associated with validation errors. */
    property?: string;
    /** Redirect target associated with this error. */
    redirect?: string;
    /** Hide the stack trace when serializing/logging. */
    hideStack?: boolean;
    /** Underlying error to wrap. Non-core properties are copied onto the GhostError. */
    err?: Error | string;
}

export class GhostError extends Error {
    statusCode: number;
    errorType: string;
    level: string;
    id: string;
    context?: string;
    help?: string;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    errorDetails: any;
    code: string | null;
    property: string | null;
    redirect: string | null;
    hideStack: boolean;

    constructor(options: GhostErrorOptions = {}) {
        super();

        /**
         * defaults
         */
        this.statusCode = 500;
        this.errorType = 'InternalServerError';
        this.level = 'normal';
        this.message = 'The server has encountered an error.';
        this.id = randomUUID();

        /**
         * custom overrides
         */
        this.id = options.id || this.id;
        this.statusCode = options.statusCode || this.statusCode;
        this.level = options.level || this.level;
        this.context = options.context;
        this.help = options.help;
        this.errorType = this.name = options.errorType || this.errorType;
        this.errorDetails = options.errorDetails;
        this.code = options.code || null;
        this.property = options.property || null;
        this.redirect = options.redirect || null;

        this.message = options.message || this.message;
        this.hideStack = options.hideStack || false;

        // NOTE: Error to inherit from, override!
        //       Nested objects are getting copied over in one piece (can be changed, but not needed right now)
        if (options.err) {
            // CASE: Support err as string (it happens that third party libs return a string instead of an error instance)
            if (typeof options.err === 'string') {
                options.err = new Error(options.err);
            }

            Object.getOwnPropertyNames(options.err).forEach((property) => {
                if (
                    ['errorType', 'name', 'statusCode', 'message', 'level'].indexOf(property) !== -1
                ) {
                    return;
                }

                // CASE: `code` should put options as priority over err
                if (property === 'code') {
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    this[property] = this[property] || (options.err as any)[property];
                    return;
                }

                if (property === 'stack' && !this.hideStack) {
                    this[property] = wrapStack(this, options.err as Error);
                    return;
                }

                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                (this as any)[property] = (options.err as any)[property] || (this as any)[property];
            });
        }
    }
}
