package com.clanstop.healing.locator;

import com.clanstop.healing.persistence.ElementStore;
import com.clanstop.healing.persistence.StoredLocator;
import org.openqa.selenium.By;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Produces an ordered set of candidate locators to try when the primary breaks:
 * <ol>
 *   <li>every locator that has previously worked for this element (history + prior heals) —
 *       this is the "look at what passed before and try that" behaviour;</li>
 *   <li>locators synthesised from the stored fingerprint (stable id, data-testid, name, text);</li>
 *   <li>a broad DOM-scan fallback marker (tag), scored element-by-element by the resolver.</li>
 * </ol>
 */
public final class CandidateGenerator {

    /** A candidate locator plus the strategy label recorded on a successful heal. */
    public record Candidate(By by, String byType, String byValue, String strategy) {}

    private final ElementStore store;

    public CandidateGenerator(ElementStore store) {
        this.store = store;
    }

    public List<Candidate> generate(String logicalKey, Fingerprint stored, By failedPrimary) {
        List<Candidate> out = new ArrayList<>();
        Set<String> seen = new LinkedHashSet<>();
        String failedKey = Locators.typeOf(failedPrimary) + "=" + Locators.valueOf(failedPrimary);

        // 1) History (most recent / most successful first). Includes prior heal winners.
        for (StoredLocator loc : store.history(logicalKey)) {
            String key = loc.byType() + "=" + loc.byValue();
            if (key.equals(failedKey) || !seen.add(key)) {
                continue;
            }
            try {
                out.add(new Candidate(Locators.build(loc.byType(), loc.byValue()),
                        loc.byType(), loc.byValue(), "HISTORY"));
            } catch (IllegalArgumentException ignored) {
                // Unknown persisted type — skip.
            }
        }

        // 2) Synthesised from the stored fingerprint.
        if (stored != null) {
            addSynth(out, seen, "id", stored.id(), By.id(stored.id()));
            String testId = stored.attrs().get("data-testid");
            if (notBlank(testId)) {
                addSynth(out, seen, "cssSelector", "[data-testid=\"" + testId + "\"]",
                        By.cssSelector("[data-testid=\"" + testId + "\"]"));
            }
            addSynth(out, seen, "name", stored.name(), By.name(stored.name()));
            if (notBlank(stored.visibleText())) {
                String txt = stored.visibleText().replace("\"", "");
                String xp = "//" + safeTag(stored.tag())
                        + "[normalize-space(.)=\"" + txt + "\"]";
                addSynth(out, seen, "xpath", xp, By.xpath(xp));
            }
        }
        return out;
    }

    private void addSynth(List<Candidate> out, Set<String> seen, String type, String value, By by) {
        if (!notBlank(value)) {
            return;
        }
        String key = type + "=" + value;
        if (seen.add(key)) {
            out.add(new Candidate(by, type, value, "FINGERPRINT"));
        }
    }

    private static String safeTag(String tag) {
        return notBlank(tag) ? tag : "*";
    }

    private static boolean notBlank(String s) {
        return s != null && !s.isBlank();
    }
}
