"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.OperationFingerprintService = void 0;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const operation_schema_1 = require("@myycontext/core/src/types/operation-schema");
/**
 * OperationFingerprint Service
 * Provides deterministic hashing and idempotency checking for operations
 */
class OperationFingerprintService {
    constructor(baseDir) {
        this.baseDir = baseDir || process.cwd();
        this.fingerprintDbPath = path.join(this.baseDir, ".mycontext", "operation-fingerprints.json");
        this.fingerprintDb = new Map();
    }
    /**
     * Initialize: Load fingerprint database
     */
    async init() {
        if (await fs.pathExists(this.fingerprintDbPath)) {
            const data = await fs.readJson(this.fingerprintDbPath);
            this.fingerprintDb = new Map(Object.entries(data));
        }
        else {
            await fs.ensureDir(path.dirname(this.fingerprintDbPath));
            await this.persist();
        }
    }
    /**
     * Generate fingerprint for an operation
     */
    generateFingerprint(op) {
        // Create deterministic representation: type + sorted params
        const canonical = this.canonicalize(op);
        return crypto.createHash("sha256").update(canonical).digest("hex");
    }
    /**
     * Record operation fingerprint
     */
    async recordFingerprint(op, applied = false) {
        const fingerprint = this.generateFingerprint(op);
        const paramsHash = this.hashParams(op.params);
        const record = {
            fingerprint,
            operationId: op.id,
            operationType: op.type,
            paramsHash,
            timestamp: new Date().toISOString(),
            applied,
            appliedAt: applied ? new Date().toISOString() : undefined,
            appliedBy: applied ? op.id : undefined,
        };
        this.fingerprintDb.set(fingerprint, record);
        await this.persist();
        return record;
    }
    /**
     * Mark operation as applied
     */
    async markApplied(op) {
        const fingerprint = this.generateFingerprint(op);
        const existing = this.fingerprintDb.get(fingerprint);
        if (existing) {
            existing.applied = true;
            existing.appliedAt = new Date().toISOString();
            existing.appliedBy = op.id;
        }
        else {
            await this.recordFingerprint(op, true);
        }
        await this.persist();
    }
    /**
     * Check if operation has been applied
     */
    isApplied(op) {
        const fingerprint = this.generateFingerprint(op);
        const record = this.fingerprintDb.get(fingerprint);
        return record?.applied || false;
    }
    /**
     * Check idempotency
     */
    async checkIdempotency(op) {
        const schema = (0, operation_schema_1.getOperationSchema)(op.type);
        const level = schema.idempotencyLevel;
        const applied = this.isApplied(op);
        switch (level) {
            case "idempotent":
                return {
                    isIdempotent: true,
                    canRetry: true,
                    canSkip: applied,
                    reason: applied
                        ? "Operation already applied and is idempotent - safe to skip"
                        : "Operation is idempotent - safe to apply multiple times",
                    level,
                };
            case "conditional":
                // Need to check specific conditions
                const conditionCheck = await this.checkConditionalIdempotency(op);
                return {
                    isIdempotent: conditionCheck.safe,
                    canRetry: conditionCheck.safe,
                    canSkip: applied && conditionCheck.safe,
                    reason: conditionCheck.reason,
                    level,
                };
            case "non-idempotent":
                return {
                    isIdempotent: false,
                    canRetry: false,
                    canSkip: false,
                    reason: applied
                        ? "Operation already applied and is non-idempotent - DO NOT retry"
                        : "Operation is non-idempotent - can only be applied once",
                    level,
                };
            default:
                return {
                    isIdempotent: false,
                    canRetry: false,
                    canSkip: false,
                    reason: "Unknown idempotency level",
                    level,
                };
        }
    }
    /**
     * Detect duplicates
     */
    detectDuplicate(op, allOps) {
        const fingerprint = this.generateFingerprint(op);
        for (const other of allOps) {
            if (other.id === op.id)
                continue;
            const otherFingerprint = this.generateFingerprint(other);
            if (fingerprint === otherFingerprint) {
                return {
                    isDuplicate: true,
                    duplicateOf: other.id,
                    fingerprint,
                    suggestion: `This operation is identical to ${other.id}. Consider removing duplicate.`,
                };
            }
        }
        return {
            isDuplicate: false,
            fingerprint,
            suggestion: "No duplicates detected",
        };
    }
    /**
     * Get fingerprint record
     */
    getFingerprint(op) {
        const fingerprint = this.generateFingerprint(op);
        return this.fingerprintDb.get(fingerprint);
    }
    /**
     * Get all fingerprints
     */
    getAllFingerprints() {
        return Array.from(this.fingerprintDb.values());
    }
    /**
     * Check if operation can be safely skipped
     */
    async canSkip(op) {
        const idempotencyCheck = await this.checkIdempotency(op);
        if (idempotencyCheck.canSkip) {
            return {
                skip: true,
                reason: `Operation already applied and is ${idempotencyCheck.level}`,
            };
        }
        // Additional skip checks
        switch (op.type) {
            case "add-import": {
                // Check if import already exists
                const file = op.params.file;
                const import_ = op.params.import_;
                const absPath = path.resolve(this.baseDir, file);
                if (await fs.pathExists(absPath)) {
                    const content = await fs.readFile(absPath, "utf-8");
                    if (content.includes(import_)) {
                        return {
                            skip: true,
                            reason: `Import "${import_}" already exists in ${file}`,
                        };
                    }
                }
                break;
            }
            case "create-file": {
                // Check if file already exists with same content
                const filePath = op.params.path;
                const absPath = path.resolve(this.baseDir, filePath);
                const expectedContent = op.params.content;
                if (await fs.pathExists(absPath)) {
                    const existingContent = await fs.readFile(absPath, "utf-8");
                    if (existingContent === expectedContent) {
                        return {
                            skip: true,
                            reason: `File ${filePath} already exists with identical content`,
                        };
                    }
                    else if (!op.params.overwrite) {
                        return {
                            skip: false,
                            reason: `File ${filePath} exists with different content and overwrite=false`,
                        };
                    }
                }
                break;
            }
            case "delete-file": {
                // Check if file already deleted
                const filePath = op.params.path;
                const absPath = path.resolve(this.baseDir, filePath);
                if (!(await fs.pathExists(absPath))) {
                    return {
                        skip: true,
                        reason: `File ${filePath} already deleted`,
                    };
                }
                break;
            }
        }
        return {
            skip: false,
            reason: "Operation must be executed",
        };
    }
    /**
     * Clear fingerprint database
     */
    async clear() {
        this.fingerprintDb.clear();
        await this.persist();
    }
    /**
     * Canonicalize operation for hashing
     */
    canonicalize(op) {
        // Sort params keys for determinism
        const sortedParams = this.sortObject(op.params);
        return JSON.stringify({
            type: op.type,
            params: sortedParams,
        });
    }
    /**
     * Sort object keys recursively
     */
    sortObject(obj) {
        if (obj === null || obj === undefined)
            return obj;
        if (typeof obj !== "object")
            return obj;
        if (Array.isArray(obj)) {
            return obj.map((item) => this.sortObject(item));
        }
        const sorted = {};
        const keys = Object.keys(obj).sort();
        for (const key of keys) {
            sorted[key] = this.sortObject(obj[key]);
        }
        return sorted;
    }
    /**
     * Hash params
     */
    hashParams(params) {
        const canonical = JSON.stringify(this.sortObject(params));
        return crypto.createHash("sha256").update(canonical).digest("hex");
    }
    /**
     * Check conditional idempotency
     */
    async checkConditionalIdempotency(op) {
        switch (op.type) {
            case "create-file": {
                const overwrite = op.params.overwrite;
                return {
                    safe: overwrite === true,
                    reason: overwrite
                        ? "Overwrite enabled - idempotent"
                        : "Overwrite disabled - only safe if file doesn't exist",
                };
            }
            case "add-classname": {
                // Safe if class is added only if not already present
                const file = op.params.file;
                const classname = op.params.classname;
                const absPath = path.resolve(this.baseDir, file);
                if (await fs.pathExists(absPath)) {
                    const content = await fs.readFile(absPath, "utf-8");
                    const alreadyHasClass = content.includes(classname);
                    return {
                        safe: !alreadyHasClass,
                        reason: alreadyHasClass
                            ? "Class already exists - skip to avoid duplication"
                            : "Class does not exist - safe to add",
                    };
                }
                return { safe: true, reason: "File doesn't exist yet" };
            }
            case "add-route":
            case "add-component":
            case "add-action":
            case "add-model": {
                // Safe if file doesn't exist yet
                return {
                    safe: true,
                    reason: "Safe if output file doesn't exist",
                };
            }
            default:
                return {
                    safe: false,
                    reason: "Conditional idempotency not determined",
                };
        }
    }
    /**
     * Persist fingerprint database
     */
    async persist() {
        const data = Object.fromEntries(this.fingerprintDb);
        await fs.writeJson(this.fingerprintDbPath, data, { spaces: 2 });
    }
}
exports.OperationFingerprintService = OperationFingerprintService;
//# sourceMappingURL=OperationFingerprint.js.map