package com.clanstop.healing.driver;

import com.clanstop.healing.locator.LocatorResolver;
import com.clanstop.healing.persistence.ElementStore;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.util.List;
import java.util.Set;

/**
 * A drop-in {@link WebDriver} decorator for existing projects. Wrap your real driver and
 * <em>nothing else in your test code changes</em> — every {@code findElement(By.id/.cssSelector/.xpath(...))}
 * automatically fingerprints on success and self-heals on failure, keyed by the locator itself.
 *
 * <pre>{@code
 *   WebDriver driver = new SelfHealingWebDriver(new ChromeDriver());
 *   driver.findElement(By.id("username")).sendKeys("alice");   // heals if #username moved/renamed
 * }</pre>
 *
 * <p>Healing is keyed on {@code by.toString()} (e.g. {@code "By.id: username"}), which stays stable
 * as long as your test keeps the same locator — exactly the case where the <em>page</em> changed but
 * your code did not. If you want history to survive changing the locator in your own code too, use
 * {@link HealingDriver#find(ByKey, By)} with an explicit stable key instead.
 *
 * <p>Implements {@link JavascriptExecutor} and {@link TakesScreenshot} as well, since many
 * frameworks (waits, screenshot listeners) cast the driver to them.
 */
public final class SelfHealingWebDriver implements WebDriver, JavascriptExecutor, TakesScreenshot {

    private final WebDriver delegate;
    private final LocatorResolver resolver;

    public SelfHealingWebDriver(WebDriver delegate) {
        this.delegate = delegate;
        // The resolver works against the RAW delegate, so healing never re-enters this decorator.
        this.resolver = new LocatorResolver(delegate, new ElementStore());
        // Register for failure screenshots taken by the step-logging plugin.
        HealingContext.setCurrentDriver(delegate);
    }

    /** The one intercepted method: heal-or-throw, same {@code NoSuchElementException} contract. */
    @Override
    public WebElement findElement(By by) {
        return resolver.find(by.toString(), by);
    }

    /** findElements is left un-healed: an empty list is often an intentional absence check. */
    @Override
    public List<WebElement> findElements(By by) {
        return delegate.findElements(by);
    }

    public WebDriver raw() {
        return delegate;
    }

    // --- straight delegation below ---

    @Override public void get(String url)               { delegate.get(url); }
    @Override public String getCurrentUrl()             { return delegate.getCurrentUrl(); }
    @Override public String getTitle()                  { return delegate.getTitle(); }
    @Override public String getPageSource()             { return delegate.getPageSource(); }
    @Override public void close()                       { delegate.close(); }
    @Override public void quit()                        { delegate.quit(); }
    @Override public Set<String> getWindowHandles()     { return delegate.getWindowHandles(); }
    @Override public String getWindowHandle()           { return delegate.getWindowHandle(); }
    @Override public TargetLocator switchTo()           { return delegate.switchTo(); }
    @Override public Navigation navigate()              { return delegate.navigate(); }
    @Override public Options manage()                   { return delegate.manage(); }

    @Override public Object executeScript(String script, Object... args) {
        return ((JavascriptExecutor) delegate).executeScript(script, args);
    }

    @Override public Object executeAsyncScript(String script, Object... args) {
        return ((JavascriptExecutor) delegate).executeAsyncScript(script, args);
    }

    @Override public <X> X getScreenshotAs(OutputType<X> target) {
        return ((TakesScreenshot) delegate).getScreenshotAs(target);
    }
}
