001package gudusoft.gsqlparser.resolver2.model;
002
003import gudusoft.gsqlparser.nodes.TParseTreeNode;
004import gudusoft.gsqlparser.resolver2.inference.EvidenceType;
005
006/**
007 * Unified evidence model for column resolution.
008 *
009 * <p>This class consolidates the scattered evidence/confidence information
010 * from various resolver components into a single, consistent structure.
011 * It provides:
012 * <ul>
013 *   <li>Structured evidence type (enum, not string magic values)</li>
014 *   <li>Confidence weight with clear semantics</li>
015 *   <li>Traceability through source location and node</li>
016 *   <li>Human-readable messages for debugging</li>
017 * </ul>
018 *
019 * <h3>Confidence Score Semantics</h3>
020 * The confidence score represents "how verifiable is this resolution":
021 * <ul>
022 *   <li><b>1.0 (Definite)</b>: Hard evidence - DDL/metadata/explicit definition</li>
023 *   <li><b>~0.9 (High-confidence)</b>: Qualified reference without metadata verification</li>
024 *   <li><b>~0.7 (Inferred)</b>: Star column inference, CTE reverse fill</li>
025 *   <li><b>~0.5 (Weak)</b>: Context-based guess, should not auto-resolve</li>
026 * </ul>
027 *
028 * @see EvidenceType
029 * @see ColumnSource
030 */
031public class ResolutionEvidence {
032
033    /** Type of evidence */
034    private final EvidenceType type;
035
036    /** Confidence weight [0.0, 1.0] */
037    private final double weight;
038
039    /** Human-readable description of how evidence was obtained */
040    private final String message;
041
042    /** Location in source where this evidence originates */
043    private final SourceLocation location;
044
045    /** The AST node that provides this evidence (optional, for debugging) */
046    private final TParseTreeNode sourceNode;
047
048    /**
049     * Create a new ResolutionEvidence with all fields.
050     *
051     * @param type The evidence type
052     * @param weight Confidence weight [0.0, 1.0]
053     * @param message Human-readable description
054     * @param location Source location
055     * @param sourceNode The AST node (optional)
056     */
057    public ResolutionEvidence(EvidenceType type, double weight, String message,
058                               SourceLocation location, TParseTreeNode sourceNode) {
059        this.type = type != null ? type : EvidenceType.INFERRED_FROM_USAGE;
060        this.weight = Math.max(0.0, Math.min(1.0, weight));
061        this.message = message;
062        this.location = location;
063        this.sourceNode = sourceNode;
064    }
065
066    /**
067     * Create a ResolutionEvidence without source node.
068     */
069    public ResolutionEvidence(EvidenceType type, double weight, String message, SourceLocation location) {
070        this(type, weight, message, location, null);
071    }
072
073    /**
074     * Create a ResolutionEvidence from an AST node.
075     */
076    public ResolutionEvidence(EvidenceType type, double weight, String message, TParseTreeNode node) {
077        this(type, weight, message, node != null ? new SourceLocation(node) : null, node);
078    }
079
080    /**
081     * Create a simple ResolutionEvidence with just type and weight.
082     */
083    public ResolutionEvidence(EvidenceType type, double weight) {
084        this(type, weight, null, (SourceLocation) null, null);
085    }
086
087    // ========== Factory Methods for Common Evidence Types ==========
088
089    /**
090     * Create evidence for metadata-backed resolution (DDL, SQLEnv).
091     * Confidence: 1.0 (definite)
092     */
093    public static ResolutionEvidence fromMetadata(String tableName, String columnName, TParseTreeNode node) {
094        return new ResolutionEvidence(
095            EvidenceType.METADATA,
096            1.0,
097            "Column '" + columnName + "' found in metadata for table '" + tableName + "'",
098            node
099        );
100    }
101
102    /**
103     * Create evidence for DDL-defined column (CREATE TABLE in same script).
104     * Confidence: 1.0 (definite)
105     */
106    public static ResolutionEvidence fromDDL(String tableName, String columnName, TParseTreeNode node) {
107        return new ResolutionEvidence(
108            EvidenceType.METADATA,
109            1.0,
110            "Column '" + columnName + "' defined in DDL for table '" + tableName + "'",
111            node
112        );
113    }
114
115    /**
116     * Create evidence for qualified reference (table.column).
117     * Confidence: 0.95 if we have metadata, 0.9 otherwise
118     */
119    public static ResolutionEvidence fromQualifiedReference(String tableName, String columnName,
120                                                             boolean hasMetadata, TParseTreeNode node) {
121        double confidence = hasMetadata ? 0.95 : 0.9;
122        return new ResolutionEvidence(
123            EvidenceType.QUALIFIED_REFERENCE,
124            confidence,
125            "Qualified reference: " + tableName + "." + columnName,
126            node
127        );
128    }
129
130    /**
131     * Create evidence for unqualified reference.
132     * Confidence depends on number of tables in scope.
133     */
134    public static ResolutionEvidence fromUnqualifiedReference(String columnName, int tableCount,
135                                                               TParseTreeNode node) {
136        double confidence = tableCount == 1 ? 0.8 : 0.5;
137        return new ResolutionEvidence(
138            EvidenceType.UNQUALIFIED_REFERENCE,
139            confidence,
140            "Unqualified reference: " + columnName + " (" + tableCount + " tables in scope)",
141            node
142        );
143    }
144
145    /**
146     * Create evidence for CTE explicit column list.
147     * Confidence: 1.0 (explicit definition)
148     */
149    public static ResolutionEvidence fromCTEExplicitColumn(String cteName, String columnName,
150                                                            TParseTreeNode node) {
151        return new ResolutionEvidence(
152            EvidenceType.CTE_COLUMN_LIST,
153            1.0,
154            "Explicit CTE column: " + cteName + "(" + columnName + ")",
155            node
156        );
157    }
158
159    /**
160     * Create evidence for CTE implicit column (from subquery).
161     * Confidence: 1.0 (defined in subquery)
162     */
163    public static ResolutionEvidence fromCTEImplicitColumn(String cteName, String columnName,
164                                                            TParseTreeNode node) {
165        return new ResolutionEvidence(
166            EvidenceType.SUBQUERY_RESULT,
167            1.0,
168            "CTE implicit column from subquery: " + cteName + "." + columnName,
169            node
170        );
171    }
172
173    /**
174     * Create evidence for star column inference (outer reference pushdown).
175     * Confidence: 0.7-0.9 depending on context
176     */
177    public static ResolutionEvidence fromStarInference(String columnName, String namespaceName,
178                                                        boolean isQualified, TParseTreeNode node) {
179        double confidence = isQualified ? 0.9 : 0.7;
180        return new ResolutionEvidence(
181            EvidenceType.INFERRED_FROM_USAGE,
182            confidence,
183            "Inferred from outer reference to star namespace: " + namespaceName + "." + columnName,
184            node
185        );
186    }
187
188    /**
189     * Create evidence for JOIN condition.
190     * Confidence: 0.9 (explicit relationship)
191     */
192    public static ResolutionEvidence fromJoinCondition(String tableName, String columnName,
193                                                        TParseTreeNode node) {
194        return new ResolutionEvidence(
195            EvidenceType.JOIN_CONDITION,
196            0.9,
197            "JOIN condition: " + tableName + "." + columnName,
198            node
199        );
200    }
201
202    /**
203     * Create evidence for INSERT column list.
204     * Confidence: 1.0 (explicit definition)
205     */
206    public static ResolutionEvidence fromInsertColumnList(String tableName, String columnName,
207                                                           TParseTreeNode node) {
208        return new ResolutionEvidence(
209            EvidenceType.INSERT_COLUMN_LIST,
210            1.0,
211            "INSERT column: " + tableName + "." + columnName,
212            node
213        );
214    }
215
216    /**
217     * Create evidence for UPDATE SET clause.
218     * Confidence: 0.95 (explicit assignment)
219     */
220    public static ResolutionEvidence fromUpdateSet(String tableName, String columnName,
221                                                    TParseTreeNode node) {
222        return new ResolutionEvidence(
223            EvidenceType.UPDATE_SET,
224            0.95,
225            "UPDATE SET: " + tableName + "." + columnName,
226            node
227        );
228    }
229
230    /**
231     * Create evidence for SELECT list.
232     * Confidence: 0.85
233     */
234    public static ResolutionEvidence fromSelectList(String tableName, String columnName,
235                                                     TParseTreeNode node) {
236        return new ResolutionEvidence(
237            EvidenceType.SELECT_LIST,
238            0.85,
239            "SELECT list: " + tableName + "." + columnName,
240            node
241        );
242    }
243
244    /**
245     * Create evidence for struct field access (BigQuery/Snowflake).
246     * Confidence: inherits from base column resolution
247     */
248    public static ResolutionEvidence fromStructFieldAccess(String baseColumn, String fieldPath,
249                                                            double baseConfidence, TParseTreeNode node) {
250        return new ResolutionEvidence(
251            EvidenceType.INFERRED_FROM_USAGE,
252            baseConfidence,
253            "Struct field access: " + baseColumn + "." + fieldPath,
254            node
255        );
256    }
257
258    /**
259     * Create evidence for guess strategy application.
260     * Confidence: 0.7 (guessed from candidates)
261     */
262    public static ResolutionEvidence fromGuessStrategy(String columnName, String chosenTable,
263                                                        String strategy, TParseTreeNode node) {
264        return new ResolutionEvidence(
265            EvidenceType.INFERRED_FROM_USAGE,
266            0.7,
267            "Guessed by " + strategy + " strategy: " + chosenTable + "." + columnName,
268            node
269        );
270    }
271
272    /**
273     * Create evidence from legacy string evidence.
274     * This is for backward compatibility during migration.
275     *
276     * @deprecated Use specific factory methods instead
277     */
278    @Deprecated
279    public static ResolutionEvidence fromLegacyEvidence(String legacyEvidence, double confidence,
280                                                         TParseTreeNode node) {
281        EvidenceType type = inferTypeFromLegacyEvidence(legacyEvidence);
282        return new ResolutionEvidence(type, confidence, legacyEvidence, node);
283    }
284
285    /**
286     * Infer EvidenceType from legacy string evidence.
287     */
288    private static EvidenceType inferTypeFromLegacyEvidence(String evidence) {
289        if (evidence == null) {
290            return EvidenceType.INFERRED_FROM_USAGE;
291        }
292
293        String lower = evidence.toLowerCase();
294
295        if (lower.contains("metadata") || lower.contains("ddl") || lower.contains("sqlenv")) {
296            return EvidenceType.METADATA;
297        }
298        if (lower.contains("qualified") || lower.contains("table.column")) {
299            return EvidenceType.QUALIFIED_REFERENCE;
300        }
301        if (lower.contains("cte_explicit")) {
302            return EvidenceType.CTE_COLUMN_LIST;
303        }
304        if (lower.contains("cte_implicit") || lower.contains("subquery")) {
305            return EvidenceType.SUBQUERY_RESULT;
306        }
307        if (lower.contains("join")) {
308            return EvidenceType.JOIN_CONDITION;
309        }
310        if (lower.contains("insert")) {
311            return EvidenceType.INSERT_COLUMN_LIST;
312        }
313        if (lower.contains("update") || lower.contains("set")) {
314            return EvidenceType.UPDATE_SET;
315        }
316        if (lower.contains("select")) {
317            return EvidenceType.SELECT_LIST;
318        }
319        if (lower.contains("outer_reference") || lower.contains("star") || lower.contains("inferred")) {
320            return EvidenceType.INFERRED_FROM_USAGE;
321        }
322
323        return EvidenceType.UNQUALIFIED_REFERENCE;
324    }
325
326    // ========== Getters ==========
327
328    public EvidenceType getType() {
329        return type;
330    }
331
332    public double getWeight() {
333        return weight;
334    }
335
336    public String getMessage() {
337        return message;
338    }
339
340    public SourceLocation getLocation() {
341        return location;
342    }
343
344    public TParseTreeNode getSourceNode() {
345        return sourceNode;
346    }
347
348    // ========== Utility Methods ==========
349
350    /**
351     * Check if this evidence represents a definite resolution.
352     * Definite means confidence >= 1.0 (has hard evidence like DDL/metadata).
353     */
354    public boolean isDefinite() {
355        return weight >= 1.0;
356    }
357
358    /**
359     * Check if this evidence represents a high-confidence resolution.
360     * High-confidence means confidence >= 0.9.
361     */
362    public boolean isHighConfidence() {
363        return weight >= 0.9;
364    }
365
366    /**
367     * Check if this evidence represents an inferred resolution.
368     * Inferred means confidence < 1.0 or type is INFERRED_FROM_USAGE.
369     */
370    public boolean isInferred() {
371        return weight < 1.0 || type == EvidenceType.INFERRED_FROM_USAGE;
372    }
373
374    /**
375     * Create a copy with updated weight.
376     */
377    public ResolutionEvidence withWeight(double newWeight) {
378        return new ResolutionEvidence(type, newWeight, message, location, sourceNode);
379    }
380
381    /**
382     * Create a copy with appended message.
383     */
384    public ResolutionEvidence withAppendedMessage(String additionalMessage) {
385        String newMessage = message != null
386            ? message + "; " + additionalMessage
387            : additionalMessage;
388        return new ResolutionEvidence(type, weight, newMessage, location, sourceNode);
389    }
390
391    /**
392     * Convert to legacy string evidence format.
393     * For backward compatibility with existing code.
394     */
395    public String toLegacyEvidence() {
396        // Map type to legacy string
397        switch (type) {
398            case METADATA:
399                if (message != null && message.toLowerCase().contains("ddl")) {
400                    return "ddl_metadata";
401                }
402                return "metadata";
403            case QUALIFIED_REFERENCE:
404                return "qualified_reference";
405            case UNQUALIFIED_REFERENCE:
406                return "unqualified_reference";
407            case CTE_COLUMN_LIST:
408                return "cte_explicit_column";
409            case SUBQUERY_RESULT:
410                return "cte_implicit_column";
411            case JOIN_CONDITION:
412                return "join_condition";
413            case INSERT_COLUMN_LIST:
414                return "insert_column_list";
415            case UPDATE_SET:
416                return "update_set";
417            case SELECT_LIST:
418                return "select_list";
419            case INFERRED_FROM_USAGE:
420                if (message != null) {
421                    if (message.contains("outer_reference") || message.contains("outer reference")) {
422                        return "outer_reference";
423                    }
424                    if (message.contains("star")) {
425                        return "star_push_down";
426                    }
427                    if (message.contains("guess") || message.contains("strategy")) {
428                        if (message.contains("nearest")) return "guess_strategy_nearest";
429                        if (message.contains("farthest")) return "guess_strategy_farthest";
430                    }
431                    if (message.contains("struct_field") || message.contains("Struct field")) {
432                        return "struct_field_access";
433                    }
434                }
435                return "inferred_from_usage";
436            default:
437                return message != null ? message : "unknown";
438        }
439    }
440
441    @Override
442    public String toString() {
443        StringBuilder sb = new StringBuilder();
444        sb.append("ResolutionEvidence{type=").append(type);
445        sb.append(", weight=").append(String.format("%.2f", weight));
446        if (message != null) {
447            sb.append(", msg='").append(message).append("'");
448        }
449        if (location != null) {
450            sb.append(", loc=").append(location);
451        }
452        sb.append("}");
453        return sb.toString();
454    }
455
456    @Override
457    public boolean equals(Object obj) {
458        if (this == obj) return true;
459        if (!(obj instanceof ResolutionEvidence)) return false;
460
461        ResolutionEvidence other = (ResolutionEvidence) obj;
462        return type == other.type &&
463               Double.compare(weight, other.weight) == 0 &&
464               (message == null ? other.message == null : message.equals(other.message));
465    }
466
467    @Override
468    public int hashCode() {
469        int result = type.hashCode();
470        long temp = Double.doubleToLongBits(weight);
471        result = 31 * result + (int) (temp ^ (temp >>> 32));
472        result = 31 * result + (message != null ? message.hashCode() : 0);
473        return result;
474    }
475}