/**
 * indexLoadPositionAnalysis.js
 *
 * Provides a document-scan that mirrors the load-position tracking done in
 * indexUsageValidator.js, without the diagnostic overhead.  Consumed by the
 * hover and completion providers so that they can show the developer which
 * index segment a given INDEX_LOAD_KEY_* call corresponds to.
 */

import { extractArgAt } from "../parser.js";

/**
 * Maps INDEX_LOAD_KEY_* and SQL_INDEX_LOAD_KEY_* call names → set of segment types they can satisfy.
 * @type {Record<string, Set<string>>}
 */
const LOAD_KEY_SUFFIX_TO_TYPES = {
    "INDEX_LOAD_KEY_STRING":     new Set(["STRING", "ZSTRING"]),
    "INDEX_LOAD_KEY_SHORT":      new Set(["SHORT"]),
    "INDEX_LOAD_KEY_LONG":       new Set(["LONG"]),
    "INDEX_LOAD_KEY_DATE":       new Set(["DATE"]),
    "SQL_INDEX_LOAD_KEY_STRING": new Set(["STRING", "ZSTRING"]),
    "SQL_INDEX_LOAD_KEY_SHORT":  new Set(["SHORT"]),
    "SQL_INDEX_LOAD_KEY_LONG":   new Set(["LONG"]),
    "SQL_INDEX_LOAD_KEY_DATE":   new Set(["DATE"])
};

/**
 * Maps a segment type to the canonical INDEX_LOAD_KEY_* suffix.
 * @type {Record<string, string>}
 */
const TYPE_TO_LOAD_KEY_SUFFIX = {
    "STRING":  "STRING",
    "ZSTRING": "STRING",
    "SHORT":   "SHORT",
    "LONG":    "LONG",
    "DATE":    "DATE",
    "BIGINT":  "LONG"
};

/**
 * Scans lines 0..(targetLine - 1) and returns a map of handle variable name
 * (lowercase) → current index state at the START of targetLine.
 *
 * State shape:
 *   { tableCode: string, indexName: string, nextLoadPosition: number }
 *
 * `nextLoadPosition` is the count of INDEX_LOAD_KEY_* calls since the last
 * INDEX_OPEN_* or INDEX_CLEAR_KEY for that handle — i.e. the position index
 * that the NEXT load call on that handle will fill.
 *
 * @param {object} document   VS Code TextDocument (or compatible plain object)
 * @param {number} targetLine Line to scan up to (exclusive)
 * @returns {Map<string, {tableCode: string, indexName: string, nextLoadPosition: number}>}
 */
function buildLoadPositionsAtLine(document, targetLine) {
    /** @type {Map<string, {tableCode: string, indexName: string, nextLoadPosition: number}>} */
    const positions = new Map();
    const limit = Math.min(targetLine, document.lineCount);

    for (let i = 0; i < limit; i++) {
        const lineText = document.lineAt(i).text;
        const CALL_RE = /\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
        let match;

        while ((match = CALL_RE.exec(lineText)) !== null) {
            const callName = match[1].toUpperCase();
            const callExpr = lineText.slice(match.index);

            if (callName.startsWith("INDEX_OPEN_") || callName === "SQL_INDEX_OPEN_STANDARD") {
                const tableArg  = extractArgAt(callExpr, 0);
                const indexArg  = extractArgAt(callExpr, 1);
                const handleArg = extractArgAt(callExpr, 2);

                if (tableArg && indexArg && handleArg) {
                    const tableMatch = tableArg.trim().match(/^["']([^"']+)["']$/);
                    if (tableMatch) {
                        const tableCode = tableMatch[1].toUpperCase();
                        const indexName = indexArg.trim().replace(/^["']|["']$/g, "");
                        const handleVar = handleArg.trim().toLowerCase();

                        if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(handleVar)) {
                            positions.set(handleVar, { tableCode, indexName, nextLoadPosition: 0 });
                        }
                    }
                }

            } else if (callName === "INDEX_CLEAR_KEY" || callName === "SQL_INDEX_CLEAR_KEY") {
                const handleArg = extractArgAt(callExpr, 0);
                if (handleArg) {
                    const entry = positions.get(handleArg.trim().toLowerCase());
                    if (entry) entry.nextLoadPosition = 0;
                }

            } else if (callName === "INDEX_CLOSE" || callName === "SQL_INDEX_CLOSE") {
                const handleArg = extractArgAt(callExpr, 0);
                if (handleArg) {
                    positions.delete(handleArg.trim().toLowerCase());
                }

            } else if (LOAD_KEY_SUFFIX_TO_TYPES[callName]) {
                const handleArg = extractArgAt(callExpr, 0);
                if (handleArg) {
                    const entry = positions.get(handleArg.trim().toLowerCase());
                    if (entry) entry.nextLoadPosition += 1;
                }
            }
        }
    }

    return positions;
}

/**
 * Given a table structure object and a field word (either the handle form
 * `hXXy_Name` or the raw index-segment form `XXy_Name`), returns metadata
 * about the field and which indexes it appears in.
 *
 * Returns null if the field cannot be resolved in this structure.
 *
 * @param {object} structure  Table structure from tableStructureRegistry
 * @param {string} word       Field name as it appears in the source
 * @returns {{ canonical: string, type: string, appearances: Array<{indexName: string, position: number, type: string, length: number}> }|null}
 */
function resolveFieldInStructure(structure, word) {
    const lowerWord = word.toLowerCase();

    // Handle / output form: word starts with 'h' followed by 2-letter table code
    if (structure.fields && structure.fields[lowerWord]) {
        const entry = structure.fields[lowerWord];
        // The raw field name in index segments is the canonical name without the leading 'h'
        const rawFieldName = entry.canonical.slice(1); // strip leading 'h'
        const appearances = [];
        for (const idx of structure.indexes) {
            for (const seg of idx.segments) {
                if (seg.field.toLowerCase() === rawFieldName.toLowerCase()) {
                    appearances.push({
                        indexName: idx.name,
                        position: seg.position,
                        type: seg.type,
                        length: seg.length
                    });
                }
            }
        }
        return { canonical: entry.canonical, type: entry.type, appearances };
    }

    // Raw / index-segment form: scan all segment field names
    const appearances = [];
    let firstType = null;
    let firstLength = null;
    for (const idx of structure.indexes) {
        for (const seg of idx.segments) {
            if (seg.field.toLowerCase() === lowerWord) {
                if (firstType === null) {
                    firstType = seg.type;
                    firstLength = seg.length;
                }
                appearances.push({
                    indexName: idx.name,
                    position: seg.position,
                    type: seg.type,
                    length: seg.length
                });
            }
        }
    }

    if (appearances.length > 0) {
        return { canonical: word, type: firstType, length: firstLength, appearances };
    }

    return null;
}

export { buildLoadPositionsAtLine, resolveFieldInStructure, LOAD_KEY_SUFFIX_TO_TYPES, TYPE_TO_LOAD_KEY_SUFFIX };
