import { DEFAULT_TASK_INTAKE_POLICY, TASK_POLICY_OVERRIDE_PRECEDENCE } from "./policy-config.js";
const SAFE_FIELD_PATH_RE = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*$/;
const PROFILE_METADATA_KEY = "taskIntakeProfile";
function isRecord(value) {
    return value !== null && typeof value === "object" && !Array.isArray(value);
}
function cloneDefaultConfig() {
    return JSON.parse(JSON.stringify(DEFAULT_TASK_INTAKE_POLICY));
}
function parseIntakeEnforcementMode(raw) {
    if (raw === "off" || raw === "advisory" || raw === "enforce" || raw === "enforce-on-accept") {
        return raw;
    }
    return undefined;
}
function stringArray(raw) {
    return Array.isArray(raw) ? raw.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
}
function parseFieldRules(raw) {
    if (!isRecord(raw)) {
        return {};
    }
    const out = {};
    for (const [path, value] of Object.entries(raw)) {
        if (!SAFE_FIELD_PATH_RE.test(path) || !isRecord(value)) {
            continue;
        }
        const rule = {};
        for (const key of ["minItems", "minLength", "maxLength", "itemMinLength"]) {
            const v = value[key];
            if (typeof v === "number" && Number.isInteger(v) && v >= 0) {
                rule[key] = v;
            }
        }
        if (Array.isArray(value.allowedValues)) {
            rule.allowedValues = [...value.allowedValues];
        }
        const requiresAny = stringArray(value.requiresAny);
        if (requiresAny.length > 0) {
            rule.requiresAny = requiresAny;
        }
        out[path] = rule;
    }
    return out;
}
function parseProfile(raw, fallback) {
    const base = fallback ?? DEFAULT_TASK_INTAKE_POLICY.profiles.advisory;
    if (!isRecord(raw)) {
        return { ...base, fieldRules: { ...base.fieldRules } };
    }
    return {
        requiredFields: stringArray(raw.requiredFields),
        recommendedFields: stringArray(raw.recommendedFields),
        forbiddenFields: stringArray(raw.forbiddenFields),
        fieldRules: parseFieldRules(raw.fieldRules),
        enforcementMode: parseIntakeEnforcementMode(raw.enforcementMode) ?? base.enforcementMode
    };
}
export function parseTaskIntakePolicyConfig(effective) {
    const rawTasks = effective?.tasks;
    const raw = isRecord(rawTasks) ? rawTasks.intakePolicy : undefined;
    if (!isRecord(raw)) {
        return cloneDefaultConfig();
    }
    const defaults = cloneDefaultConfig();
    const profiles = { ...defaults.profiles };
    if (isRecord(raw.profiles)) {
        for (const [name, profile] of Object.entries(raw.profiles)) {
            profiles[name] = parseProfile(profile, profiles[name]);
        }
    }
    const moduleOverrides = {};
    if (isRecord(raw.moduleOverrides)) {
        for (const [moduleId, overrideRaw] of Object.entries(raw.moduleOverrides)) {
            if (isRecord(overrideRaw) && typeof overrideRaw.profile === "string") {
                moduleOverrides[moduleId] = {
                    profile: overrideRaw.profile,
                    enforcementMode: parseIntakeEnforcementMode(overrideRaw.enforcementMode)
                };
            }
        }
    }
    return {
        defaultProfile: typeof raw.defaultProfile === "string" ? raw.defaultProfile : defaults.defaultProfile,
        enforcementMode: parseIntakeEnforcementMode(raw.enforcementMode) ?? defaults.enforcementMode,
        profiles,
        moduleOverrides
    };
}
function normalizeProfile(profile) {
    return {
        requiredFields: [...new Set(profile.requiredFields)],
        recommendedFields: [...new Set(profile.recommendedFields)],
        forbiddenFields: [...new Set(profile.forbiddenFields)],
        fieldRules: { ...profile.fieldRules },
        enforcementMode: profile.enforcementMode
    };
}
function nonEmptyString(value) {
    return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
function fieldPresent(value) {
    if (value === undefined || value === null) {
        return false;
    }
    if (typeof value === "string") {
        return value.trim().length > 0;
    }
    if (Array.isArray(value)) {
        return value.length > 0;
    }
    if (isRecord(value)) {
        return Object.keys(value).length > 0;
    }
    return true;
}
function readFieldPath(source, path) {
    if (!SAFE_FIELD_PATH_RE.test(path)) {
        return undefined;
    }
    const parts = path.split(".");
    let current = source;
    for (const part of parts) {
        if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, part)) {
            return undefined;
        }
        current = current[part];
    }
    return current;
}
function buildEvaluationSource(input) {
    const task = input.task ?? undefined;
    const explicit = isRecord(input.fields) ? input.fields : {};
    const metadata = {
        ...(isRecord(task?.metadata) ? task?.metadata : {}),
        ...(isRecord(input.metadata) ? input.metadata : {}),
        ...(isRecord(explicit.metadata) ? explicit.metadata : {})
    };
    return {
        ...(task ?? {}),
        ...explicit,
        id: task?.id ?? input.taskId ?? explicit.id,
        type: input.type ?? explicit.type ?? task?.type,
        status: input.targetStatus ?? explicit.status ?? task?.status,
        phaseKey: input.phaseKey ?? explicit.phaseKey ?? task?.phaseKey,
        metadata
    };
}
function valuesEqual(a, b) {
    return JSON.stringify(a) === JSON.stringify(b);
}
function evaluateRule(field, rule, value, source) {
    const violations = [];
    if (rule.minItems !== undefined && (!Array.isArray(value) || value.length < rule.minItems)) {
        violations.push({ field, rule: "minItems", message: `${field} must include at least ${rule.minItems} item(s)` });
    }
    if (rule.minLength !== undefined && (typeof value !== "string" || value.trim().length < rule.minLength)) {
        violations.push({ field, rule: "minLength", message: `${field} must be at least ${rule.minLength} character(s)` });
    }
    if (rule.maxLength !== undefined && typeof value === "string" && value.length > rule.maxLength) {
        violations.push({ field, rule: "maxLength", message: `${field} must be at most ${rule.maxLength} character(s)` });
    }
    if (rule.itemMinLength !== undefined &&
        (!Array.isArray(value) ||
            value.some((entry) => typeof entry !== "string" || entry.trim().length < rule.itemMinLength))) {
        violations.push({
            field,
            rule: "itemMinLength",
            message: `${field} items must be at least ${rule.itemMinLength} character(s)`
        });
    }
    if (rule.allowedValues !== undefined && !rule.allowedValues.some((entry) => valuesEqual(entry, value))) {
        violations.push({ field, rule: "allowedValues", message: `${field} must be one of the configured allowed values` });
    }
    if (rule.requiresAny !== undefined && !rule.requiresAny.some((path) => fieldPresent(readFieldPath(source, path)))) {
        violations.push({
            field,
            rule: "requiresAny",
            message: `${field} requires at least one of: ${rule.requiresAny.join(", ")}`
        });
    }
    return violations;
}
export function resolveTaskIntakePolicy(input) {
    const cfg = parseTaskIntakePolicyConfig(input.effectiveConfig);
    const explain = [];
    const warnings = [];
    const task = input.task ?? undefined;
    const moduleId = nonEmptyString(input.moduleId);
    const metadata = isRecord(input.metadata) ? input.metadata : isRecord(task?.metadata) ? task?.metadata : {};
    const metadataProfile = nonEmptyString(metadata[PROFILE_METADATA_KEY]);
    const moduleOverride = moduleId ? cfg.moduleOverrides[moduleId] : undefined;
    const explicitType = nonEmptyString(input.type) ?? nonEmptyString(input.fields?.type) ?? task?.type ?? null;
    const explicitAction = nonEmptyString(input.action) ?? "create-task";
    const explicitTargetStatus = nonEmptyString(input.targetStatus) ?? nonEmptyString(input.fields?.status) ?? task?.status ?? null;
    const explicitCategory = nonEmptyString(input.category) ?? nonEmptyString(metadata.category);
    const explicitPhaseKey = nonEmptyString(input.phaseKey) ?? nonEmptyString(input.fields?.phaseKey) ?? task?.phaseKey ?? null;
    let profileName = cfg.defaultProfile;
    let profileSource = "workspace-default";
    if (metadataProfile && cfg.profiles[metadataProfile]) {
        profileName = metadataProfile;
        profileSource = "task-metadata";
    }
    else if (metadataProfile) {
        warnings.push({
            code: "unknown-task-metadata-profile",
            message: `metadata.${PROFILE_METADATA_KEY} '${metadataProfile}' is not defined in tasks.intakePolicy.profiles — falling back to workspace default`
        });
    }
    if (profileSource !== "task-metadata" && moduleOverride?.profile && cfg.profiles[moduleOverride.profile]) {
        profileName = moduleOverride.profile;
        profileSource = "module-override";
    }
    const contextProfileCandidates = [
        explicitAction && explicitTargetStatus ? `${explicitAction}-${explicitTargetStatus}` : null,
        explicitType && explicitAction ? `${explicitType}-${explicitAction}` : null,
        explicitType && explicitTargetStatus ? `${explicitType}-${explicitTargetStatus}` : null,
        explicitCategory ? `category-${explicitCategory}` : null,
        explicitPhaseKey ? `phase-${explicitPhaseKey}` : null,
        explicitType
    ].filter((entry) => typeof entry === "string" && entry.length > 0);
    if (profileSource === "workspace-default") {
        const contextProfileName = contextProfileCandidates.find((candidate) => cfg.profiles[candidate]);
        if (contextProfileName) {
            profileName = contextProfileName;
            profileSource = contextProfileName === "improvement" ? "built-in-default" : "context-match";
            explain.push({ key: "contextProfileMatch", value: contextProfileName, source: profileSource });
        }
    }
    if (!cfg.profiles[profileName]) {
        profileName = "advisory";
        profileSource = "built-in-default";
        warnings.push({
            code: "unknown-effective-profile",
            message: "Resolved task intake profile missing from config — using built-in 'advisory'"
        });
    }
    const profile = normalizeProfile(cfg.profiles[profileName]);
    let enforcementMode = profile.enforcementMode ?? cfg.enforcementMode;
    explain.push({ key: "profileName", value: profileName, source: profileSource });
    explain.push({ key: "tasks.intakePolicy.enforcementMode", value: cfg.enforcementMode, source: "workspace-default" });
    if (profile.enforcementMode) {
        explain.push({ key: "profile.enforcementMode", value: profile.enforcementMode, source: profileSource });
    }
    if (moduleOverride?.enforcementMode) {
        enforcementMode = moduleOverride.enforcementMode;
        explain.push({ key: "tasks.intakePolicy.enforcementMode", value: enforcementMode, source: "module-override" });
    }
    if (cfg.enforcementMode === "off") {
        enforcementMode = "off";
    }
    const source = buildEvaluationSource(input);
    const action = explicitAction;
    const context = {
        taskId: nonEmptyString(input.taskId) ?? task?.id ?? null,
        type: explicitType ?? nonEmptyString(source.type) ?? null,
        targetStatus: explicitTargetStatus ?? nonEmptyString(source.status) ?? null,
        moduleId,
        category: explicitCategory,
        phaseKey: explicitPhaseKey ?? nonEmptyString(source.phaseKey) ?? null
    };
    if (enforcementMode === "off") {
        return {
            resolvedPolicy: {
                schemaVersion: 1,
                profileName,
                enforcementMode: "off",
                action,
                context,
                requiredFields: [],
                recommendedFields: [],
                forbiddenFields: [],
                fieldRules: {}
            },
            missingRequiredFields: [],
            missingRecommendedFields: [],
            forbiddenPresentFields: [],
            fieldRuleViolations: [],
            explain: [],
            warnings: [],
            precedenceOrder: [...TASK_POLICY_OVERRIDE_PRECEDENCE]
        };
    }
    const missingRequiredFields = profile.requiredFields.filter((path) => !fieldPresent(readFieldPath(source, path)));
    const missingRecommendedFields = profile.recommendedFields.filter((path) => !fieldPresent(readFieldPath(source, path)));
    const forbiddenPresentFields = profile.forbiddenFields.filter((path) => fieldPresent(readFieldPath(source, path)));
    const fieldRuleViolations = Object.entries(profile.fieldRules).flatMap(([path, rule]) => evaluateRule(path, rule, readFieldPath(source, path), source));
    return {
        resolvedPolicy: {
            schemaVersion: 1,
            profileName,
            enforcementMode,
            action,
            context,
            requiredFields: profile.requiredFields,
            recommendedFields: profile.recommendedFields,
            forbiddenFields: profile.forbiddenFields,
            fieldRules: profile.fieldRules
        },
        missingRequiredFields,
        missingRecommendedFields,
        forbiddenPresentFields,
        fieldRuleViolations,
        explain,
        warnings,
        precedenceOrder: [...TASK_POLICY_OVERRIDE_PRECEDENCE]
    };
}
