package org.openautonomousconnection.webserver.runtime;

import org.openautonomousconnection.webserver.api.Route;
import org.openautonomousconnection.webserver.api.WebPage;

import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Scans the content folder for .java files, compiles them and maps @Route paths to classes/files.
 *
 * <p>Behavior:
 * <ul>
 *   <li>If class has @Route AND implements WebPage => routable</li>
 *   <li>If class is compiled but not routable => still cached as loaded, but never served</li>
 * </ul>
 */
public final class JavaRouteRegistry {

    private static final class RouteEntry {
        final File sourceFile;
        final long lastModified;
        final String fqcn;
        final boolean routable;

        RouteEntry(File sourceFile, long lastModified, String fqcn, boolean routable) {
            this.sourceFile = sourceFile;
            this.lastModified = lastModified;
            this.fqcn = fqcn;
            this.routable = routable;
        }
    }

    private final JavaPageCache cache = new JavaPageCache();
    private final ConcurrentHashMap<String, RouteEntry> routes = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Long> scanState = new ConcurrentHashMap<>();

    /**
     * Refreshes registry when content folder changed.
     *
     * @param contentRoot content root
     */
    public void refreshIfNeeded(File contentRoot) {
        if (contentRoot == null) return;
        long lm = folderLastModified(contentRoot);

        Long prev = scanState.get("lm");
        if (prev != null && prev == lm && !routes.isEmpty()) return;

        scanState.put("lm", lm);
        rebuild(contentRoot);
    }

    /**
     * Finds a routable class for a route.
     *
     * @param route route path
     * @return entry or null
     */
    public RouteLookupResult find(String route) {
        RouteEntry e = routes.get(route);
        if (e == null) return null;
        return new RouteLookupResult(e.sourceFile, e.fqcn, e.routable);
    }

    private void rebuild(File contentRoot) {
        routes.clear();

        List<File> javaFiles = listJavaFiles(contentRoot);
        for (File f : javaFiles) {
            try {
                long lm = f.lastModified();
                Class<?> clazz = cache.getOrCompile(f);

                Route r = clazz.getAnnotation(Route.class);
                boolean isWebPage = WebPage.class.isAssignableFrom(clazz);
                boolean routable = (r != null && isWebPage);

                if (r != null) {
                    String path = normalizeRoute(r.path());
                    routes.put(path, new RouteEntry(f, lm, clazz.getName(), routable));
                }

            } catch (Exception ignored) {
                // Compilation errors are handled later when requested or during next refresh.
            }
        }
    }

    private static String normalizeRoute(String value) {
        if (value == null) return "/";
        String v = value.trim();
        if (v.isEmpty()) return "/";
        if (!v.startsWith("/")) v = "/" + v;
        return v;
    }

    private static List<File> listJavaFiles(File root) {
        ArrayList<File> out = new ArrayList<>();
        Deque<File> stack = new ArrayDeque<>();
        stack.push(root);

        while (!stack.isEmpty()) {
            File cur = stack.pop();
            File[] children = cur.listFiles();
            if (children == null) continue;

            for (File c : children) {
                if (c.isDirectory()) {
                    stack.push(c);
                } else if (c.isFile() && c.getName().endsWith(".java")) {
                    out.add(c);
                }
            }
        }

        return out;
    }

    private static long folderLastModified(File folder) {
        long lm = folder.lastModified();
        File[] children = folder.listFiles();
        if (children == null) return lm;
        for (File c : children) {
            lm = Math.max(lm, c.isDirectory() ? folderLastModified(c) : c.lastModified());
        }
        return lm;
    }

    /**
     * Lookup result for a route.
     *
     * @param sourceFile Java source file
     * @param fqcn fully qualified class name
     * @param routable true if @Route + implements WebPage
     */
    public record RouteLookupResult(File sourceFile, String fqcn, boolean routable) {}
}
