import fs from "node:fs/promises";
import path from "node:path";
import z from "zod";
import { needsAppProviderFixInAppTsx } from "../../../ensure-app-provider.js";
import { getLogger } from "../../../util/logger.js";
import { Paths } from "../../const.js";
import { createToolFactory, ToolCategory, ToolExecutionError, } from "../tools2/types.js";
import { debugCache, createTypeScriptCheckFunction, createRuntimeAndBuildErrorsFunction, } from "./debug-cache.js";
export const buildDebugToolFactory = createToolFactory("build_debug", ({ clark, services, }) => {
    return {
        category: ToolCategory.GENERAL,
        description: `
      Run type checking and application validation on the current code.
      Call this before finalizing to ensure there are no errors.

      REQUIRED: You must call this tool at least once before calling build_finalize.

      IMPORTANT: This tool provides the most accurate results when called shortly after
      making code changes. The system automatically invalidates cached results when files
      are modified to ensure you always get fresh validation data.
    `,
        inputSchema: z.object({}),
        execute: async () => {
            const now = Date.now();
            const STALE_ERROR_THRESHOLD = 2 * 60 * 1000;
            const RECENT_MODIFICATION_THRESHOLD = 5 * 1000; // 5 seconds
            const isTypecheckEnabled = clark.context.llmConfig?.flags?.isTypecheckEnabled ?? true;
            // Check if files were recently modified
            const lastModTime = clark.context.lastFileModificationTime || 0;
            const timeSinceLastModification = now - lastModTime;
            const filesRecentlyModified = timeSinceLastModification < RECENT_MODIFICATION_THRESHOLD;
            // If files were recently modified, invalidate TypeScript cache to force fresh check
            if (filesRecentlyModified) {
                debugCache.invalidateTypeScript();
                getLogger().info(`[build_debug] Files modified ${timeSinceLastModification}ms ago, forcing fresh TypeScript check`);
            }
            let staleBuildErrorsDropped = 0;
            if (clark.context.buildSystemErrors?.length) {
                const freshErrors = clark.context.buildSystemErrors.filter((e) => {
                    const ts = e.timestamp || 0;
                    const errorAge = now - ts;
                    const occurredBeforeLatestEdit = ts < lastModTime;
                    const keep = errorAge < STALE_ERROR_THRESHOLD && !occurredBeforeLatestEdit;
                    if (!keep)
                        staleBuildErrorsDropped++;
                    return keep;
                });
                if (freshErrors.length !== clark.context.buildSystemErrors.length) {
                    clark.updateContext((context) => ({
                        ...context,
                        buildSystemErrors: freshErrors,
                    }));
                    debugCache.invalidateContextErrors();
                }
            }
            // Use the debug cache to get combined results
            const typeCheckFunction = isTypecheckEnabled
                ? createTypeScriptCheckFunction(services.appShell, clark.context.abortController?.signal)
                : async () => ({
                    status: "success",
                    timestamp: Date.now(),
                    typeErrors: "",
                });
            const contextErrorsFunction = createRuntimeAndBuildErrorsFunction(clark, services.appShell);
            const result = await debugCache.getCombinedResult(typeCheckFunction, contextErrorsFunction);
            let appProviderBuildError = null;
            try {
                const appTsxPath = path.join(services.appShell.appRootDirPath, Paths.AppRoot);
                const appTsx = await fs.readFile(appTsxPath, "utf-8");
                if (needsAppProviderFixInAppTsx(appTsx)) {
                    appProviderBuildError = {
                        message: "CRITICAL: App.tsx must import `AppProvider` from `@superblocksteam/library` and wrap the components in `App.tsx`. (Import `{ App as AppProvider }` and ensure it wraps the `<Outlet />`.)",
                        timestamp: now,
                        age: 0,
                    };
                }
            }
            catch (e) {
                const message = e instanceof Error ? e.message : String(e);
                getLogger().debug(`[build_debug] Failed to validate App.tsx AppProvider wrapper: ${message}`);
            }
            const effectiveResult = appProviderBuildError
                ? {
                    ...result,
                    status: "errors_found",
                    message: result.status === "success"
                        ? "Found errors that need to be fixed"
                        : result.message,
                    buildSystemErrors: [
                        ...(result.buildSystemErrors ?? []),
                        appProviderBuildError,
                    ],
                }
                : result;
            if (!isTypecheckEnabled) {
                effectiveResult.message =
                    effectiveResult.message +
                        "\n\nNote: Type checking skipped because `isTypecheckEnabled` is false for this generation.";
            }
            // Track runtime errors in history
            const errorTracker = debugCache.getErrorHistoryTracker();
            const currentErrorKeys = new Set();
            for (const err of effectiveResult.runtimeErrors) {
                const errorKey = err.fingerprint
                    ? String(err.fingerprint)
                    : `${String(err.error)}::${err.componentType || "unknown"}`;
                currentErrorKeys.add(errorKey);
                errorTracker.recordError(errorKey);
            }
            // Check for errors that disappeared without being fixed
            const disappeared = errorTracker.getUnresolvedDisappearances(currentErrorKeys);
            // Add warnings about disappeared errors to result
            const warnings = [];
            // Warn about failed API builds that haven't been rebuilt
            if (clark.context.failedApiBuilds?.size) {
                warnings.push(`${clark.context.failedApiBuilds.size} API build(s) have failed and not been rebuilt:`);
                for (const apiName of clark.context.failedApiBuilds) {
                    warnings.push(`   - ${apiName}`);
                }
                warnings.push("", "These APIs must be successfully rebuilt before you can finalize.");
            }
            if (disappeared.length > 0) {
                warnings.push(`${disappeared.length} runtime error(s) that were seen earlier are no longer appearing:`);
                for (const hist of disappeared) {
                    warnings.push(`   - "${hist.errorKey}" (seen ${hist.seenCount} time(s), last seen ${Math.round((Date.now() - hist.lastSeen) / 1000)}s ago)`);
                }
                warnings.push("", "These errors may not have been fixed - they might only be hidden because:", "   - Component is rendering with different data now", "   - Error boundary recovered", "   - Component is in a different state", "", "Verify these errors are actually fixed by:", "   1. Reviewing code that accesses these properties", "   2. Adding optional chaining (?.) and fallback values", "   3. Testing with null/undefined data states");
            }
            // Block finalization if any APIs have not been tested
            const untestedApis = clark.context.apiTestingState?.untested;
            if (untestedApis?.size) {
                const untestedList = Array.from(untestedApis)
                    .map((api) => `   - ${api}`)
                    .join("\n");
                throw new ToolExecutionError(`${untestedApis.size} API(s) have not been tested yet:\n${untestedList}\n\n` +
                    "You must call testApi for each untested API before finalizing.", {
                    toolName: "build_debug",
                    input: {},
                    context: {},
                    severity: "warn",
                });
            }
            // Add warning about failed API tests
            const failedApiTests = clark.context.apiTestingState?.failed;
            if (failedApiTests?.size) {
                if (warnings.length > 0) {
                    warnings.push(""); // Add spacing between warning sections
                }
                warnings.push(`WARNING: ${failedApiTests.size} API test(s) failed:`);
                for (const apiName of failedApiTests) {
                    warnings.push(`   - ${apiName}`);
                }
                warnings.push("", "These failures may indicate bugs in your changes. Review the test output and fix any issues before finalizing.");
            }
            // Only mark as resolved on a clean run: no runtime errors and no disappearances
            if ((result.runtimeErrors?.length ?? 0) === 0 &&
                disappeared.length === 0) {
                errorTracker.markAllNotInCurrentAsResolved(currentErrorKeys);
            }
            // Update context with debug info (but don't clear errors!)
            clark.updateContext({
                debugToolCalled: true,
                lastDebugTimestamp: result.timestamp,
                lastDebugHadTypeErrors: Boolean(effectiveResult.typeErrors),
            });
            // Return result with warnings if applicable
            if (warnings.length > 0) {
                return {
                    ...effectiveResult,
                    message: effectiveResult.message + "\n\n" + warnings.join("\n"),
                };
            }
            // If we dropped stale build errors but have no other warnings, surface a brief diagnostic note
            if (staleBuildErrorsDropped > 0) {
                return {
                    ...effectiveResult,
                    message: effectiveResult.message +
                        `\n\nNote: ${staleBuildErrorsDropped} stale build error(s) were filtered because they predated the latest edits or exceeded the staleness threshold.`,
                };
            }
            return effectiveResult;
        },
        readOnly: true,
        getSummary: (_input, output) => {
            const typeErrors = output.typeErrors ? 1 : 0;
            const buildErrors = output.buildSystemErrors?.length ?? 0;
            const runtimeErrors = output.runtimeErrors?.length ?? 0;
            const total = typeErrors + buildErrors + runtimeErrors;
            if (total === 0) {
                return { output: "All checks passed" };
            }
            const parts = [];
            if (typeErrors > 0)
                parts.push(`${typeErrors} TS`);
            if (buildErrors > 0)
                parts.push(`${buildErrors} build`);
            if (runtimeErrors > 0)
                parts.push(`${runtimeErrors} runtime`);
            return {
                output: `${parts.join(", ")} error${total === 1 ? "" : "s"}`,
            };
        },
    };
});
//# sourceMappingURL=build-debug.js.map