/**
 * Spellmatic — Harper Bridge  (Phase 2)
 *
 * Loads the Harper grammar/spell-check engine using the WASM binary that is
 * already inlined (base64) inside harper.js — no CDN, no external requests,
 * works on every hosting platform.
 *
 * Harper is open-source under the Apache-2.0 license.
 * See LICENSE-harper.txt in this directory for full license text.
 *
 * Exposes:
 *   window.__spellmaticHarper.ready  → Promise<{ lintText(text): Promise<error[]> }>
 *
 * ── Quality filter strategy ──────────────────────────────────────────────────
 * Harper is a rule-based checker — it doesn't understand context.
 * We apply six filters before surfacing results to reduce false positives:
 *
 *  1. Skip Typo / WordChoice lint kinds — produce incorrect word-split suggestions
 *     (e.g. "gettin → get tin", "his s → hiss").
 *  2. Skip single-character Spelling errors — nearly always wrong in context.
 *  3. Skip all-uppercase words — likely acronyms, brand names, abbreviations
 *     (e.g. "XYZ → Xi").
 *  4. Skip suggestions that introduce a space inside a solid word — always wrong
 *     (e.g. "gettin → get tin", "goed → go ed").
 *  5. Skip words where the suggestion is a strict prefix removing >1 character —
 *     catches CamelCase brand names (e.g. "Elementor → Element", "WordPress → Word").
 *     Single-character suffix removal is kept (e.g. "Wonderfull → Wonderful" is valid).
 *  6. Skip words in the site's custom dictionary (brand names, proper nouns, etc.)
 *     Loaded from window.spellmatic.dictionary (set via wp_localize_script).
 * ─────────────────────────────────────────────────────────────────────────────
 */

// URL injected by PHP (class-harper.php) — local in dev, jsDelivr CDN in production.
const _harperUrl = ( window.spellmatic && window.spellmatic.harperJsUrl )
    ? window.spellmatic.harperJsUrl
    : new URL( './harper.js', import.meta.url ).href;

const { LocalLinter, binaryInlined } = await import( _harperUrl );

// Lint kinds that consistently produce low-quality suggestions.
const SKIP_KINDS = new Set( [ 'typo', 'word_choice', 'wordchoice' ] );

// Phase 4: custom dictionary — words to always ignore.
// Populated from window.spellmatic.dictionary (wp_localize_script) as lowercase strings.
// Falls back to empty set if not present (e.g. on first load before settings save).
const _rawDict  = ( window.spellmatic && window.spellmatic.dictionary ) || [];
const CUSTOM_DICT = new Set( _rawDict.map( w => String( w ).toLowerCase() ) );

const _readyPromise = ( async () => {

    const linter = new LocalLinter( { binary: binaryInlined } );
    await linter.setup();

    return {

        /**
         * Lint plain text. Returns filtered, structured error objects.
         * @param {string} text  Clean plain text (HTML stripped server-side).
         * @returns {Promise<Array>}
         */
        lintText: async function( text ) {
            if ( ! text || ! text.trim() ) return [];

            const lints  = await linter.lint( text );
            const errors = [];

            for ( const lint of lints ) {
                try {
                    const rawKind = lint.lint_kind_pretty ? lint.lint_kind_pretty() : 'Spelling';
                    const kind    = rawKind.toLowerCase().replace( /\s+/g, '_' );

                    // ── Quality filters ─────────────────────────────────────────

                    // 1. Skip noisy lint kinds
                    if ( SKIP_KINDS.has( kind ) ) continue;

                    const original = lint.get_problem_text();

                    // 2. Skip single-character Spelling errors (too many false positives)
                    if ( original.length === 1 && kind === 'spelling' ) continue;

                    // 3. Skip all-uppercase words — acronyms, brand names, abbreviations
                    if ( /^[A-Z0-9]{2,}$/.test( original ) ) continue;

                    // 4. Skip suggestions that split a solid word with a space
                    //    e.g. "gettin → get tin" — nearly always wrong
                    const suggestions = lint.suggestions();
                    const replacement = suggestions.length > 0
                        ? suggestions[ 0 ].get_replacement_text()
                        : '';

                    if ( replacement.includes( ' ' ) && ! original.includes( ' ' ) ) continue;

                    // 5. Skip CamelCase brand names where the suggestion is a strict prefix
                    //    that removes more than one character from the end.
                    //    e.g. "Elementor → Element" (removes "or"), "WordPress → Word" (removes "Press")
                    //    But keep genuine double-letter fixes: "Wonderfull → Wonderful" (removes 1 char)
                    if (
                        replacement.length > 0 &&
                        original.startsWith( replacement ) &&
                        ( original.length - replacement.length ) > 1
                    ) continue;

                    // 6. Skip words in the site's custom dictionary.
                    //    Comparison is case-insensitive so "Elementor", "elementor", "ELEMENTOR"
                    //    all match a dictionary entry of "elementor".
                    if ( CUSTOM_DICT.size > 0 && CUSTOM_DICT.has( original.toLowerCase() ) ) continue;

                    // ── Build context (sentence containing the error) ────────────

                    const span  = lint.span();   // span() is a METHOD, returns { start, end }
                    const start = span.start;
                    const end   = span.end;

                    const prevPunct = Math.max(
                        text.lastIndexOf( '. ', start ),
                        text.lastIndexOf( '! ', start ),
                        text.lastIndexOf( '? ', start ),
                        text.lastIndexOf( '\n', start )
                    );
                    const sentStart = prevPunct <= 0 ? 0 : prevPunct + 2;

                    const nextPunct = ( () => {
                        const c = [ '.', '!', '?' ].map( p => {
                            const i = text.indexOf( p + ' ', end );
                            return i === -1 ? Infinity : i + 1;
                        } );
                        const nl = text.indexOf( '\n', end );
                        c.push( nl === -1 ? Infinity : nl );
                        return Math.min( ...c );
                    } )();
                    const sentEnd = nextPunct === Infinity ? text.length : nextPunct;

                    const ctxText = text.substring(
                        sentStart,
                        Math.min( sentEnd, sentStart + 300 )
                    ).trim();

                    errors.push( {
                        original:     original,
                        suggestion:   replacement,
                        type:         kind,
                        context:      ctxText,
                        offset_start: start,
                        offset_end:   end,
                    } );

                } catch ( lintErr ) {
                    console.warn( 'Spellmatic: could not process lint result', lintErr );
                }
            }

            return errors;
        },

    };

} )();

window.__spellmaticHarper = { ready: _readyPromise };

console.log( 'Spellmatic: Harper bridge loaded, engine initialising…' );