import * as z from "zod" import { defineAction } from "../../../automation/actions" import { toGoogleBandingProperties } from "../lib/formatting" import { assertBandedRangeUpdateKeepsBanding, getGoogleSheetsApi, normalizeBandedRange, normalizeSpreadsheetId, resolveBandedRange, resolveGridRange, } from "../lib/google-sheets" import { GOOGLE_SHEETS_WRITE_REQUIREMENT } from "../lib/scopes" import { BANDED_RANGE_SCHEMA, BANDING_PROPERTIES_UPDATE_SCHEMA, GRID_RANGE_SCHEMA, SPREADSHEET_REFERENCE_SCHEMA, } from "../lib/schemas" const BANDING_UPDATE_SCHEMA = z.union([ BANDING_PROPERTIES_UPDATE_SCHEMA, z.null(), ]) /** Updates a numeric banded range's bounds or colors. */ export const updateGoogleSheetsBandedRange = defineAction( "Update Google Sheets banded range", ) .describe("Updates a banded range and returns its current metadata.") .account("google", GOOGLE_SHEETS_WRITE_REQUIREMENT) .input( z .object({ /** Numeric banded-range ID. */ bandedRangeId: z.number().int(), /** Column updates, or null to remove column banding. */ columns: BANDING_UPDATE_SCHEMA.optional(), /** Replacement one-based grid range. */ range: GRID_RANGE_SCHEMA.optional(), /** Row updates, or null to remove row banding. */ rows: BANDING_UPDATE_SCHEMA.optional(), /** Spreadsheet ID or standard Google Sheets URL. */ spreadsheet: SPREADSHEET_REFERENCE_SCHEMA, }) .refine( ({ columns, range, rows }) => columns !== undefined || range !== undefined || rows !== undefined, "At least one banded-range property must be updated.", ), ) .output(BANDED_RANGE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const sheetsApi = getGoogleSheetsApi(account.secret) const spreadsheetId = normalizeSpreadsheetId(input.spreadsheet) if (input.columns === null || input.rows === null) { assertBandedRangeUpdateKeepsBanding( await resolveBandedRange(sheetsApi, spreadsheetId, input.bandedRangeId), input.columns, input.rows, ) } await sheetsApi.spreadsheets.batchUpdate({ requestBody: { requests: [ { updateBanding: { bandedRange: { bandedRangeId: input.bandedRangeId, columnProperties: input.columns === null || input.columns === undefined ? undefined : toGoogleBandingProperties(input.columns), range: input.range ? await resolveGridRange( sheetsApi, spreadsheetId, input.range, ) : undefined, rowProperties: input.rows === null || input.rows === undefined ? undefined : toGoogleBandingProperties(input.rows), }, fields: [ input.range === undefined ? undefined : "range", ...bandingFields("columnProperties", input.columns), ...bandingFields("rowProperties", input.rows), ] .filter((field) => field !== undefined) .join(","), }, }, ], }, spreadsheetId, }) return normalizeBandedRange( await resolveBandedRange(sheetsApi, spreadsheetId, input.bandedRangeId), ) }) /** * Builds exact update-mask paths for one banding dimension. * * @param prefix - Provider row or column property path. * @param properties - Public banding update or explicit clear. */ function bandingFields( prefix: "columnProperties" | "rowProperties", properties: z.infer | undefined, ) { if (properties === undefined) return [] if (properties === null) return [prefix] return [ properties.firstBandColor === undefined ? undefined : `${prefix}.firstBandColorStyle`, properties.footerColor === undefined ? undefined : `${prefix}.footerColorStyle`, properties.headerColor === undefined ? undefined : `${prefix}.headerColorStyle`, properties.secondBandColor === undefined ? undefined : `${prefix}.secondBandColorStyle`, ].filter((field) => field !== undefined) }