import { z } from "zod";
import { ZapierValidationError } from "../../types/errors";
export function formatFieldValue(v) {
    if (v == null)
        return "";
    if (typeof v === "object") {
        try {
            return JSON.stringify(v);
        }
        catch {
            return String(v);
        }
    }
    return String(v);
}
/**
 * Convert a field ID to its numeric form for the Tables API.
 * Accepts "f6", "6", or 6 and returns 6.
 */
export function toNumericFieldId(id) {
    if (typeof id === "number")
        return id;
    const num = parseInt(id.replace(/^f/, ""), 10);
    if (isNaN(num)) {
        throw new ZapierValidationError(`Invalid field ID: "${id}". Expected a numeric ID like "f6", "6", or 6.`);
    }
    return num;
}
export function transformTableItem(apiItem) {
    return {
        id: apiItem.id,
        name: apiItem.name,
        description: apiItem.description ?? undefined,
        created_at: apiItem.created_at,
        edited_at: apiItem.edited_at,
        kind: apiItem.kind,
        account_id: String(apiItem.owner_account_public_id ?? apiItem.owner_account_id),
        profile_id: String(apiItem.owner_zapier_customuser_public_id ??
            apiItem.owner_zapier_customuser_id),
        parent_table_id: apiItem.parent_table_id ?? undefined,
    };
}
export function transformFieldItem(apiItem) {
    return {
        id: `f${apiItem.id}`,
        type: apiItem.type,
        name: apiItem.name,
        created_at: apiItem.created_at,
        edited_at: apiItem.edited_at,
        options: apiItem.options,
        config: apiItem.config,
    };
}
export function transformRecordItem(apiItem) {
    return {
        id: apiItem.id,
        data: apiItem.data,
        created_at: apiItem.created_at,
        edited_at: apiItem.edited_at,
    };
}
function formatRecordError(fieldId, err) {
    const message = err.h || err.e || "validation error";
    if (err.v !== undefined) {
        return `Field ${fieldId}: ${message} (value: ${JSON.stringify(err.v)})`;
    }
    return `Field ${fieldId}: ${message}`;
}
function formatResponseError(err) {
    const message = err.human_title || err.title || "Unknown error";
    if (err.meta && Object.keys(err.meta).length > 0) {
        const metaParts = Object.entries(err.meta)
            .map(([key, val]) => `${key}: ${JSON.stringify(val)}`)
            .join(", ");
        return `${message} (${metaParts})`;
    }
    return message;
}
/**
 * Check a record API item for field-level validation errors and throw if any
 * are present. The errors dict is keyed by field ID, and each value has
 * `{s, v, e, h}` where `h` is the human-friendly message and `e` is the
 * technical one.
 */
export function throwOnRecordErrors(record) {
    if (!record.errors)
        return;
    const messages = [];
    for (const [fieldId, error] of Object.entries(record.errors)) {
        const err = error;
        if (err.s === "error") {
            messages.push(formatRecordError(fieldId, err));
        }
    }
    if (messages.length > 0) {
        throw new ZapierValidationError(`Record ${record.id} has errors: ${messages.join("; ")}`);
    }
}
/**
 * Check an API response for top-level validation errors (in `meta.errors`)
 * and throw if any are present.
 */
export function throwOnResponseErrors(response) {
    const errors = response.meta?.errors;
    if (!errors || errors.length === 0)
        return;
    const messages = errors.map((err) => formatResponseError(err));
    throw new ZapierValidationError(messages.join("; "));
}
export const KeyModeSchema = z
    .enum(["names", "ids"])
    .optional()
    .default("names")
    .describe('How to interpret field keys in record data. "names" (default) uses human-readable field names, "ids" uses raw field IDs (f1, f2).');
const FIELD_ID_PATTERN = /^f\d+$/;
export function isFieldId(key) {
    return FIELD_ID_PATTERN.test(key);
}
export const NESTED_COMPONENTS = {
    labeled_string: new Set(["value"]),
    ai_formula: new Set(["value"]),
    link: new Set(["link", "text"]),
    linked_record: new Set(["label", "source"]),
};
async function getFieldMapping(api, tableId, keyMode) {
    if (keyMode === "ids") {
        return null;
    }
    // "names" mode — fetch from API
    const rawResponse = await api.get(`/tables/api/v1/tables/${tableId}/fields`, {
        authRequired: true,
        resource: { type: "table", id: tableId },
    });
    const data = rawResponse.data;
    // First match wins for duplicate names
    const nameToId = new Map();
    const idToName = new Map();
    const idToType = new Map();
    for (const field of data) {
        const id = `f${field.id}`;
        if (!nameToId.has(field.name)) {
            nameToId.set(field.name, id);
        }
        if (!idToName.has(id)) {
            idToName.set(id, field.name);
        }
        idToType.set(id, field.type);
    }
    return { nameToId, idToName, idToType };
}
/**
 * Resolve an array of field keys (names like "Email" or IDs like "f6", "6", 6)
 * to numeric field IDs for the API. Only fetches field metadata if any
 * non-ID keys are present.
 */
export async function resolveFieldKeys({ api, tableId, fieldKeys, }) {
    const allAreIds = fieldKeys.every((key) => typeof key === "number" || /^(f?\d+)$/.test(key));
    if (allAreIds) {
        return fieldKeys.map(toNumericFieldId);
    }
    const mapping = await getFieldMapping(api, tableId, "names");
    if (!mapping) {
        return fieldKeys.map(toNumericFieldId);
    }
    return fieldKeys.map((key) => {
        if (typeof key === "number")
            return key;
        if (FIELD_ID_PATTERN.test(key))
            return toNumericFieldId(key);
        const id = mapping.nameToId.get(key);
        if (!id) {
            throw new ZapierValidationError(`Unknown field name: "${key}". Use a valid field name or ID.`);
        }
        return toNumericFieldId(id);
    });
}
/**
 * Resolve the field mapping for a table and return a translator.
 * Fetches field metadata from the API once (when keyMode is "names"),
 * then all translate methods are synchronous.
 */
export async function createFieldKeyTranslator({ api, tableId, keyMode, }) {
    const mapping = await getFieldMapping(api, tableId, keyMode);
    return {
        translateInput(data) {
            if (!mapping)
                return data;
            const result = {};
            for (const [key, value] of Object.entries(data)) {
                if (FIELD_ID_PATTERN.test(key) && mapping.idToName.has(key)) {
                    result[key] = value;
                }
                else if (mapping.nameToId.has(key)) {
                    result[mapping.nameToId.get(key)] = value;
                }
                else {
                    result[key] = value;
                }
            }
            return result;
        },
        translateOutput(data) {
            if (!mapping)
                return data;
            const result = {};
            for (const [key, value] of Object.entries(data)) {
                if (mapping.idToName.has(key)) {
                    result[mapping.idToName.get(key)] = value;
                }
                else {
                    result[key] = value;
                }
            }
            return result;
        },
        translateFieldKey(key) {
            if (!mapping)
                return key;
            if (FIELD_ID_PATTERN.test(key) && mapping.idToName.has(key)) {
                // Auto-append component for single-component types (e.g., f3 → f3__value for labeled_string)
                const fieldType = mapping.idToType.get(key);
                if (fieldType) {
                    const components = NESTED_COMPONENTS[fieldType];
                    if (components?.size === 1) {
                        return `${key}__${[...components][0]}`;
                    }
                }
                return key;
            }
            if (mapping.nameToId.has(key)) {
                const fieldId = mapping.nameToId.get(key);
                // Auto-append component for single-component types (e.g., Status → f3__value for labeled_string)
                const fieldType = mapping.idToType.get(fieldId);
                if (fieldType) {
                    const components = NESTED_COMPONENTS[fieldType];
                    if (components?.size === 1) {
                        return `${fieldId}__${[...components][0]}`;
                    }
                }
                return fieldId;
            }
            // Handle nested component syntax (e.g., "Status__value" → "f3__value")
            const sepIndex = key.lastIndexOf("__");
            if (sepIndex > 0) {
                const prefix = key.slice(0, sepIndex);
                const component = key.slice(sepIndex + 2);
                let fieldId;
                if (FIELD_ID_PATTERN.test(prefix) && mapping.idToName.has(prefix)) {
                    fieldId = prefix;
                }
                else {
                    fieldId = mapping.nameToId.get(prefix);
                }
                if (fieldId) {
                    const fieldType = mapping.idToType.get(fieldId);
                    if (fieldType && NESTED_COMPONENTS[fieldType]?.has(component)) {
                        return `${fieldId}__${component}`;
                    }
                }
            }
            return key;
        },
    };
}
