import { clamp, escapeWhitespace } from './util.mjs';

function emit(value) {
    return (data, i) => ({
        matched: true,
        position: i,
        value: value
    });
}
function make(
f) {
    return (data, i) => ({
        matched: true,
        position: i,
        value: f(data, i)
    });
}
function action(
f) {
    return (data, i) => {
        f(data, i);
        return {
            matched: true,
            position: i,
            value: null
        };
    };
}
function fail(
data, i) {
    return { matched: false };
}
function error(message) {
    return (data, i) => {
        throw new Error((message instanceof Function) ? message(data, i) : message);
    };
}
function token(
onToken,
onEnd) {
    return (data, i) => {
        let position = i;
        let value = undefined;
        if (i < data.tokens.length) {
            value = onToken(data.tokens[i], data, i);
            if (value !== undefined) {
                position++;
            }
        }
        else {
            onEnd?.(data, i);
        }
        return (value === undefined)
            ? { matched: false }
            : {
                matched: true,
                position: position,
                value: value
            };
    };
}
function any(data, i) {
    return (i < data.tokens.length)
        ? {
            matched: true,
            position: i + 1,
            value: data.tokens[i]
        }
        : { matched: false };
}
function satisfy(
test) {
    return (data, i) => (i < data.tokens.length && test(data.tokens[i], data, i))
        ? {
            matched: true,
            position: i + 1,
            value: data.tokens[i]
        }
        : { matched: false };
}
function mapInner(r, f) {
    return (r.matched) ? ({
        matched: true,
        position: r.position,
        value: f(r.value, r.position)
    }) : r;
}
function mapOuter(r, f) {
    return (r.matched) ? f(r) : r;
}
function map(p, mapper) {
    return (data, i) => mapInner(p(data, i), (v, j) => mapper(v, data, i, j));
}
function map1(p,
mapper) {
    return (data, i) => mapOuter(p(data, i), (m) => mapper(m, data, i));
}
function peek(p, f) {
    return (data, i) => {
        const r = p(data, i);
        f(r, data, i);
        return r;
    };
}
function option(p, def) {
    return (data, i) => {
        const r = p(data, i);
        return (r.matched)
            ? r
            : {
                matched: true,
                position: i,
                value: def
            };
    };
}
function not(p) {
    return (data, i) => {
        const r = p(data, i);
        return (r.matched)
            ? { matched: false }
            : {
                matched: true,
                position: i,
                value: true
            };
    };
}
function choice(...ps) {
    return (data, i) => {
        for (const p of ps) {
            const result = p(data, i);
            if (result.matched) {
                return result;
            }
        }
        return { matched: false };
    };
}
function otherwise(pa, pb) {
    return (data, i) => {
        const r1 = pa(data, i);
        return (r1.matched)
            ? r1
            : pb(data, i);
    };
}
function longest(...ps) {
    return (data, i) => {
        let match = undefined;
        for (const p of ps) {
            const result = p(data, i);
            if (result.matched && (!match || match.position < result.position)) {
                match = result;
            }
        }
        return match || { matched: false };
    };
}
function takeWhile(p,
test) {
    return (data, i) => {
        const values = [];
        let success = true;
        do {
            const r = p(data, i);
            if (r.matched && test(r.value, values.length + 1, data, i, r.position)) {
                values.push(r.value);
                i = r.position;
            }
            else {
                success = false;
            }
        } while (success);
        return {
            matched: true,
            position: i,
            value: values
        };
    };
}
function takeUntil(p,
test) {
    return takeWhile(p, (value, n, data, i, j) => !test(value, n, data, i, j));
}
function takeWhileP(pValue, pTest) {
    return takeWhile(pValue, (value, n, data, i) => pTest(data, i).matched);
}
function takeUntilP(pValue, pTest) {
    return takeWhile(pValue, (value, n, data, i) => !pTest(data, i).matched);
}
function many(p) {
    return takeWhile(p, () => true);
}
function many1(p) {
    return ab(p, many(p), (head, tail) => [head, ...tail]);
}
function ab(pa, pb, join) {
    return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) => join(ma.value, vb, data, i, j)));
}
function left(pa, pb) {
    return ab(pa, pb, (va) => va);
}
function right(pa, pb) {
    return ab(pa, pb, (va, vb) => vb);
}
function abc(pa, pb, pc, join) {
    return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) => join(ma.value, mb.value, vc, data, i, j))));
}
function middle(pa, pb, pc) {
    return abc(pa, pb, pc, (ra, rb) => rb);
}
function all(...ps) {
    return (data, i) => {
        const result = [];
        let position = i;
        for (const p of ps) {
            const r1 = p(data, position);
            if (r1.matched) {
                result.push(r1.value);
                position = r1.position;
            }
            else {
                return { matched: false };
            }
        }
        return {
            matched: true,
            position: position,
            value: result
        };
    };
}
function skip(...ps) {
    return map(all(...ps), () => null);
}
function flatten(...ps) {
    return flatten1(all(...ps));
}
function flatten1(p) {
    return map(p, (vs) => vs.flatMap((v) => v));
}
function sepBy1(pValue, pSep) {
    return ab(pValue, many(right(pSep, pValue)), (head, tail) => [head, ...tail]);
}
function sepBy(pValue, pSep) {
    return otherwise(sepBy1(pValue, pSep), emit([]));
}
function chainReduce(acc,
f) {
    return (data, i) => {
        let loop = true;
        let acc1 = acc;
        let pos = i;
        do {
            const r = f(acc1, data, pos)(data, pos);
            if (r.matched) {
                acc1 = r.value;
                pos = r.position;
            }
            else {
                loop = false;
            }
        } while (loop);
        return {
            matched: true,
            position: pos,
            value: acc1
        };
    };
}
function reduceLeft(acc, p,
reducer) {
    return chainReduce(acc, (acc) => map(p, (v, data, i, j) => reducer(acc, v, data, i, j)));
}
function reduceRight(p, acc,
reducer) {
    return map(many(p), (vs, data, i, j) => vs.reduceRight((acc, v) => reducer(v, acc, data, i, j), acc));
}
function leftAssoc1(pLeft, pOper) {
    return chain(pLeft, (v0) => reduceLeft(v0, pOper, (acc, f) => f(acc)));
}
function rightAssoc1(pOper, pRight) {
    return ab(reduceRight(pOper, (y) => y, (f, acc) => (y) => f(acc(y))), pRight, (f, v) => f(v));
}
function leftAssoc2(pLeft, pOper, pRight) {
    return chain(pLeft, (v0) => reduceLeft(v0, ab(pOper, pRight, (f, y) => [f, y]), (acc, [f, y]) => f(acc, y)));
}
function rightAssoc2(pLeft, pOper, pRight) {
    return ab(reduceRight(ab(pLeft, pOper, (x, f) => [x, f]), (y) => y, ([x, f], acc) => (y) => f(x, acc(y))), pRight, (f, v) => f(v));
}
function condition(cond, pTrue, pFalse) {
    return (data, i) => (cond(data, i))
        ? pTrue(data, i)
        : pFalse(data, i);
}
function decide(p) {
    return (data, i) => mapOuter(p(data, i), (m1) => m1.value(data, m1.position));
}
function chain(p,
f) {
    return (data, i) => mapOuter(p(data, i), (m1) => f(m1.value, data, i, m1.position)(data, m1.position));
}
function ahead(p) {
    return (data, i) => mapOuter(p(data, i), (m1) => ({
        matched: true,
        position: i,
        value: m1.value
    }));
}
function recursive(f) {
    return function (data, i) {
        return f()(data, i);
    };
}
function start(data, i) {
    return (i !== 0)
        ? { matched: false }
        : {
            matched: true,
            position: i,
            value: true
        };
}
function end(data, i) {
    return (i < data.tokens.length)
        ? { matched: false }
        : {
            matched: true,
            position: i,
            value: true
        };
}
function remainingTokensNumber(data, i) {
    return data.tokens.length - i;
}
function parserPosition(data, i, formatToken, contextTokens = 3) {
    const len = data.tokens.length;
    const lowIndex = clamp(0, i - contextTokens, len - contextTokens);
    const highIndex = clamp(contextTokens, i + 1 + contextTokens, len);
    const tokensSlice = data.tokens.slice(lowIndex, highIndex);
    const lines = [];
    const indexWidth = String(highIndex - 1).length + 1;
    if (i < 0) {
        lines.push(`${String(i).padStart(indexWidth)} >>`);
    }
    if (0 < lowIndex) {
        lines.push('...'.padStart(indexWidth + 6));
    }
    for (let j = 0; j < tokensSlice.length; j++) {
        const index = lowIndex + j;
        lines.push(`${String(index).padStart(indexWidth)} ${(index === i ? '>' : ' ')} ${escapeWhitespace(formatToken(tokensSlice[j]))}`);
    }
    if (highIndex < len) {
        lines.push('...'.padStart(indexWidth + 6));
    }
    if (len <= i) {
        lines.push(`${String(i).padStart(indexWidth)} >>`);
    }
    return lines.join('\n');
}
function parse(parser, tokens, options, formatToken = JSON.stringify) {
    const data = { tokens: tokens, options: options };
    const result = parser(data, 0);
    if (!result.matched) {
        throw new Error('No match');
    }
    if (result.position < data.tokens.length) {
        throw new Error(`Partial match. Parsing stopped at:\n${parserPosition(data, result.position, formatToken)}`);
    }
    return result.value;
}
function tryParse(parser, tokens, options) {
    const result = parser({ tokens: tokens, options: options }, 0);
    return (result.matched)
        ? result.value
        : undefined;
}
function match(matcher, tokens, options) {
    const result = matcher({ tokens: tokens, options: options }, 0);
    return result.value;
}

export { ab, abc, action, ahead, all, all as and, any, chain, chainReduce, choice, condition, decide, skip as discard, otherwise as eitherOr, emit, end, end as eof, error, fail, flatten, flatten1, left, leftAssoc1, leftAssoc2, longest, ahead as lookAhead, make, many, many1, map, map1, match, middle, not, emit as of, option, choice as or, otherwise, parse, parserPosition, peek, recursive, reduceLeft, reduceRight, remainingTokensNumber, right, rightAssoc1, rightAssoc2, satisfy, sepBy, sepBy1, skip, many1 as some, start, takeUntil, takeUntilP, takeWhile, takeWhileP, token, tryParse };
