class CsvParser {
    next;
    csv;
    index;
    line = [];
    lines = [];
    value = '';
    error = undefined;

    parse(csv) {
        this.csv = csv.trim().replace(/\r\n/g, '\n');
        this.result = [];
        this.next = this.handleBeforeValue;
        this.index = 0;
        while (this.next && this.index < this.csv.length) {
            this.next = this.next(this);
        }
        if (this.lines.length <= 1) {
            throw new Error('Empty CSV');
        }
        return { headers: this.lines[0], rows: this.lines.slice(1) };
    }

    handleBeforeValue() {
        const isQuoted = this.csv[this.index] === '"';
        if (isQuoted) {
            this.index++;
            this.value = '';
            return this.handleQuotedValue;
        }
        return this.handleUnquotedValue;
    }

    handleUnquotedValue() {
        const commaIndex = this.csv.indexOf(',', this.index);
        const newLineIndex = this.csv.indexOf('\n', this.index);

        let nextIndex;
        if (commaIndex >= 0 && (newLineIndex < 0 || commaIndex < newLineIndex)) {
            nextIndex = commaIndex;
        } else if (newLineIndex >= 0) {
            nextIndex = newLineIndex;
        } else {
            nextIndex = this.csv.length;
        }

        const value = this.csv.substr(this.index, nextIndex - this.index);
        this.line.push(value);

        this.index = nextIndex;

        return this.handleAfterValue;
    }

    handleQuotedValue() {
        const nextQuoteIndex = this.csv.indexOf('"', this.index);
        const nextBackslashIndex = this.csv.indexOf('\\', this.index);

        if (nextQuoteIndex < 0) {
            this.index = this.csv.length;
            this.error = 'Quoted value not closed';
            return this.handleError;
        }

        if (nextBackslashIndex > 0 && nextBackslashIndex < nextQuoteIndex) {
            const charAfterBackslash = this.csv[nextBackslashIndex + 1];
            if (charAfterBackslash === '"' || charAfterBackslash === '\\') {
                this.value +=
                    this.csv.substr(this.index, nextBackslashIndex - this.index) +
                    charAfterBackslash;
                this.index = nextBackslashIndex + 2;
            } else {
                this.value += this.csv.substr(this.index, nextBackslashIndex - this.index + 1);
                this.index = nextBackslashIndex + 1;
            }
            return this.handleQuotedValue;
        }

        if (this.csv[nextQuoteIndex + 1] === '"') {
            this.value += this.csv.substr(this.index, nextQuoteIndex - this.index + 1);
            this.index = nextQuoteIndex + 2;
            return this.handleQuotedValue;
        }

        this.value += this.csv.substr(this.index, nextQuoteIndex - this.index);
        this.index = nextQuoteIndex + 1;
        this.line.push(this.value);
        this.value = '';

        return this.handleAfterValue;
    }

    handleAfterValue() {
        const hasNextValueOnThisLine = this.csv[this.index] === ',';
        this.index++;
        if (!hasNextValueOnThisLine) {
            this.lines.push(this.line);
            this.line = [];
        }
        return this.handleBeforeValue;
    }

    handleError() {
        throw new Error(this.error);
    }
}

export { CsvParser };
