"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildDataTimezonePreviewResponse = exports.buildDataTimezonePreviewSql = exports.currentUtcWallClock = void 0;
const tslib_1 = require("tslib");
const moment_timezone_1 = tslib_1.__importDefault(require("moment-timezone"));
const formatting_1 = require("../utils/formatting");
const dbt_1 = require("./dbt");
const errors_1 = require("./errors");
const timeFrames_1 = require("./timeFrames");
// Reads a bare literal through the session timezone (set by the client from the
// data timezone) and returns it as a UTC 'YYYY-MM-DD HH:mm:ss' string - the same
// path real queries use to interpret NTZ columns. The string form parses back
// unambiguously regardless of driver Date handling.
const sessionUtcStringSql = {
    [dbt_1.SupportedDbtAdapter.POSTGRES]: (w) => `to_char((TIMESTAMP '${w}')::timestamptz AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS')`,
    [dbt_1.SupportedDbtAdapter.REDSHIFT]: (w) => `to_char((TIMESTAMP '${w}')::timestamptz AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS')`,
    [dbt_1.SupportedDbtAdapter.DUCKDB]: (w) => `strftime((TIMESTAMP '${w}')::TIMESTAMPTZ AT TIME ZONE 'UTC', '%Y-%m-%d %H:%M:%S')`,
    [dbt_1.SupportedDbtAdapter.SNOWFLAKE]: (w) => `TO_CHAR(CONVERT_TIMEZONE('UTC', '${w}'::TIMESTAMP_NTZ), 'YYYY-MM-DD HH24:MI:SS')`,
    [dbt_1.SupportedDbtAdapter.DATABRICKS]: (w) => `date_format(to_utc_timestamp('${w}', current_timezone()), 'yyyy-MM-dd HH:mm:ss')`,
    [dbt_1.SupportedDbtAdapter.SPARK]: (w) => `date_format(to_utc_timestamp('${w}', current_timezone()), 'yyyy-MM-dd HH:mm:ss')`,
    [dbt_1.SupportedDbtAdapter.TRINO]: (w) => `format_datetime(CAST(TIMESTAMP '${w}' AS timestamp with time zone) AT TIME ZONE 'UTC', 'yyyy-MM-dd HH:mm:ss')`,
    [dbt_1.SupportedDbtAdapter.ATHENA]: (w) => `format_datetime(CAST(TIMESTAMP '${w}' AS timestamp with time zone) AT TIME ZONE 'UTC', 'yyyy-MM-dd HH:mm:ss')`,
    [dbt_1.SupportedDbtAdapter.CLICKHOUSE]: (w) => `toString(toTimeZone(toDateTime('${w}'), 'UTC'))`,
    // BigQuery has no session-timezone plumbing (its data tz UI is hidden), so
    // the bare value is effectively UTC.
    [dbt_1.SupportedDbtAdapter.BIGQUERY]: (w) => `FORMAT_TIMESTAMP('%Y-%m-%d %H:%M:%S', TIMESTAMP('${w}', 'UTC'), 'UTC')`,
};
const NAIVE_WALL_CLOCK_FORMAT = 'YYYY-MM-DD HH:mm:ss';
const NAIVE_WALL_CLOCK_RE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
const RENDER_FORMAT = 'YYYY-MM-DD, HH:mm:ss';
// The current instant as a bare UTC wall-clock. moment.utc keeps it independent
// of the backend's local timezone.
const currentUtcWallClock = () => moment_timezone_1.default.utc().format(NAIVE_WALL_CLOCK_FORMAT);
exports.currentUtcWallClock = currentUtcWallClock;
// The query carries no zone of its own; the session (set by the client) does
// the interpreting.
const buildDataTimezonePreviewSql = (adapterType, nowWallClock) => {
    if (!NAIVE_WALL_CLOCK_RE.test(nowWallClock)) {
        throw new errors_1.ParameterError('Invalid timestamp');
    }
    return `SELECT ${sessionUtcStringSql[adapterType](nowWallClock)} AS naive_instant`;
};
exports.buildDataTimezonePreviewSql = buildDataTimezonePreviewSql;
// Cleaner than the default millisecond format for a human-facing preview.
const renderInZone = (instant, timezone) => (0, formatting_1.formatTimestamp)(instant, timeFrames_1.TimeFrames.SECOND, false, timezone);
// Renders an instant at a fixed offset, for when we know the offset the
// warehouse used but not its zone name.
const renderAtOffset = (instant, offsetMinutes) => moment_timezone_1.default.utc(instant).utcOffset(offsetMinutes).format(`${RENDER_FORMAT} (Z)`);
// Labels a zone by its offset when we only know the offset, not the name.
const offsetLabel = (offsetMinutes) => offsetMinutes === 0
    ? 'UTC'
    : `UTC${moment_timezone_1.default.utc().utcOffset(offsetMinutes).format('Z')}`;
// Snowflake upper-cases unquoted column aliases, so look the column up
// case-insensitively rather than assuming the literal alias casing.
const readColumn = (row, name) => {
    if (name in row)
        return row[name];
    const key = Object.keys(row).find((k) => k.toLowerCase() === name.toLowerCase());
    return key === undefined ? undefined : row[key];
};
const buildDataTimezonePreviewResponse = ({ row, nowWallClock, projectTimezone, dataTimezone, }) => {
    // The instant the warehouse parsed the bare literal into (returned as UTC).
    const naiveInstant = moment_timezone_1.default.utc(readColumn(row, 'naive_instant'), NAIVE_WALL_CLOCK_FORMAT);
    // The gap between what we sent and what came back is the offset the warehouse
    // actually used - the source of truth, since some warehouses ignore the
    // session and read UTC anyway.
    const sentAsUtc = moment_timezone_1.default.utc(nowWallClock, NAIVE_WALL_CLOCK_FORMAT);
    const sessionOffsetMinutes = sentAsUtc.diff(naiveInstant, 'minutes');
    // Only label the step with the zone name if the warehouse actually used it.
    const honored = dataTimezone !== undefined &&
        moment_timezone_1.default
            .tz(nowWallClock, NAIVE_WALL_CLOCK_FORMAT, dataTimezone)
            .utcOffset() === sessionOffsetMinutes;
    // Tz-aware columns are already pinned instants, so compute in Node, not SQL.
    const awareInstant = moment_timezone_1.default.utc(nowWallClock, NAIVE_WALL_CLOCK_FORMAT);
    return {
        projectTimezone,
        dataTimezoneApplies: dataTimezone !== undefined,
        naive: {
            interpretedAs: honored && dataTimezone
                ? dataTimezone
                : offsetLabel(sessionOffsetMinutes),
            raw: sentAsUtc.format(RENDER_FORMAT),
            readAs: renderAtOffset(naiveInstant, sessionOffsetMinutes),
            rendered: renderInZone(naiveInstant, projectTimezone),
        },
        aware: {
            raw: renderInZone(awareInstant, 'UTC'),
            rendered: renderInZone(awareInstant, projectTimezone),
        },
    };
};
exports.buildDataTimezonePreviewResponse = buildDataTimezonePreviewResponse;
//# sourceMappingURL=dataTimezonePreview.js.map