/**
 * @reference https://blog.wxm.be/2023/11/25/osm-to-pmtiles-with-tilemaker.html
 * @reference https://github.com/protomaps/basemaps/issues/15#issuecomment-1436048491
 * @reference https://github.com/openmaptiles/maptiler-basic-gl-style
 */
import {
  all,
  coalesce,
  cond,
  eq,
  exp,
  get,
  gte,
  has,
  includes,
  isIn,
  isLine,
  isPoint,
  isPoly,
  linear,
  literal,
  lte,
  match,
  mzFade,
  neq,
  notIn,
  stops,
} from './expr.ts'
import { stationIconExpression } from './station-networks.ts'

const MINOR_ROAD = ['minor', 'primary', 'secondary', 'tertiary', 'trunk']
// The bridge/tunnel layers previously filtered on MINOR_ROAD alone, missing
// 'motorway' and 'service' — real motorway overpasses/tunnels (Madrid alone
// has 650+ tagged bridge=yes) simply vanished, since the plain 'road' layer
// also excludes anything with a brunnel tag.
const BRIDGE_TUNNEL_ROAD = ['motorway', 'service', ...MINOR_ROAD]

const FONT_MED = literal(['Noto Sans Medium'])
const FONT_REG = literal(['Noto Sans Regular'])

/** `hsl(0, 0%, {l}%)` shorthand for the grayscale text/halo ramps below. */
const gray = (l: number): string => `hsl(0, 0%, ${l}%)`

/**
 * Every colour the basemap uses, resolved for one theme. Both layer builders
 * below (`default` for regional sources, `WORLD_LAYERS` for the world
 * basemap) pull exclusively from this rather than embedding literals, so a
 * theme switch is just "call this again and re-apply `paint`" — see
 * `isDarkTheme`/`watchMapTheme` in `map-theme.ts` and the callers in
 * `m-map.ts`/`m-mini-map.ts`.
 *
 * Text/halo tokens follow one rule in both themes: the more prominent a
 * label was (darker on the light palette), the more it stands out on the
 * dark palette (lighter) — see the `gray()` calls below for the light→dark
 * lightness pairs.
 */
function buildPalette(dark: boolean) {
  if (!dark) {
    return {
      background: 'hsl(47, 26%, 88%)',
      backgroundAlt: 'hsl(47, 22%, 94%)',
      water: 'hsl(205, 56%, 73%)',
      grass: 'hsl(82, 46%, 72%)',
      park: 'hsl(70, 60%, 81%)',
      sand: 'hsl(54, 81%, 53%)',
      wetland: 'hsl(180, 25%, 78%)',
      rock: 'hsl(30, 8%, 72%)',
      rail: 'hsl(34, 12%, 66%)',
      residential: 'hsl(47, 13%, 86%)',
      farmland: '#eae0d0',
      roadFill: 'hsl(0, 0%, 100%)',
      roadMinorFill: '#efefef',
      path: 'hsl(0, 0%, 97%)',
      waterwayBridgeCasing: '#bbb',
      bridgeCasing: '#dedede',
      transitCase: 'hsl(0, 0%, 100%)',
      transitSubway: 'hsl(211, 20%, 30%)',
      transitTram: 'hsl(4, 80%, 50%)',
      transitLightRail: 'hsl(145, 63%, 32%)',
      transitTrain: 'hsl(145, 63%, 32%)',
      transitMonorail: 'hsl(275, 60%, 50%)',
      boundarySub: 'hsla(0, 0%, 60%, 0.5)',
      boundaryCountry: 'hsl(0, 0%, 60%)',
      textCity: gray(0),
      textCountry: gray(13),
      textStation: gray(25),
      textSettlement: gray(25),
      textRoad: gray(30),
      textTown: gray(30),
      textContinent: gray(30),
      textState: gray(35),
      textPoi: '#666',
      textOther: gray(45),
      haloStrong: 'hsl(0, 0%, 100%)',
      halo75: 'rgba(255, 255, 255, 0.75)',
      halo70: 'rgba(255, 255, 255, 0.7)',
      halo60: 'rgba(255, 255, 255, 0.6)',
    }
  }
  return {
    background: 'hsl(220, 15%, 12%)',
    backgroundAlt: 'hsl(210, 12%, 22%)',
    water: 'hsl(210, 45%, 22%)',
    grass: 'hsl(85, 30%, 20%)',
    park: 'hsl(85, 28%, 26%)',
    sand: 'hsl(42, 45%, 32%)',
    wetland: 'hsl(180, 20%, 22%)',
    rock: 'hsl(30, 8%, 28%)',
    rail: 'hsl(34, 10%, 38%)',
    residential: 'hsl(47, 10%, 16%)',
    farmland: 'hsl(35, 20%, 18%)',
    roadFill: 'hsl(220, 8%, 50%)',
    roadMinorFill: 'hsl(220, 8%, 38%)',
    path: 'hsl(220, 8%, 40%)',
    waterwayBridgeCasing: 'hsl(210, 20%, 35%)',
    bridgeCasing: 'hsl(220, 10%, 28%)',
    transitCase: 'hsl(220, 15%, 10%)',
    transitSubway: 'hsl(211, 45%, 62%)',
    transitTram: 'hsl(4, 75%, 62%)',
    transitLightRail: 'hsl(145, 45%, 52%)',
    transitTrain: 'hsl(145, 45%, 52%)',
    transitMonorail: 'hsl(275, 55%, 68%)',
    boundarySub: 'hsla(0, 0%, 55%, 0.45)',
    boundaryCountry: 'hsl(0, 0%, 58%)',
    textCity: gray(96),
    textCountry: gray(90),
    textStation: gray(82),
    textSettlement: gray(82),
    textRoad: gray(78),
    textTown: gray(78),
    textContinent: gray(78),
    textState: gray(72),
    textPoi: gray(62),
    textOther: gray(62),
    haloStrong: 'hsl(220, 15%, 10%)',
    halo75: 'hsla(220, 15%, 10%, 0.75)',
    halo70: 'hsla(220, 15%, 10%, 0.7)',
    halo60: 'hsla(220, 15%, 10%, 0.6)',
  }
}

/** The basemap's land colour — kept in step with the mini-map backdrop and
 * the `m-map #map` pre-tiles backdrop in theme.css. */
export function mapBackground(dark: boolean): string {
  return buildPalette(dark).background
}

const WATER_OPACITY = cond(['==', get('intermittent'), 1], 0.7, 1)
const waterLine = (color: string) => ({
  'line-color': color,
  'line-opacity': linear([8, 0], mzFade(8, 9), mzFade(11, 12), [13, 1]),
  'line-width': stops(1.4, [8, 1], [20, 8]),
})

export default function (sourceName: string, dark: boolean) {
  const p = buildPalette(dark)
  return [
    {
      'id': `${sourceName}-landuse-residential`,
      'type': 'fill',
      'source': sourceName,
      'source-layer': 'landuse',
      'minzoom': 11,
      'filter': all(isPoly, eq('class', 'residential')),
      'layout': { 'visibility': 'visible' },
      'paint': { 'fill-color': p.residential, 'fill-opacity': 0.7 },
    },
    {
      'id': `${sourceName}-landcover`,
      'type': 'fill',
      'source': sourceName,
      'source-layer': 'landcover',
      'filter': includes('class', ['grass', 'wood', 'sand']),
      'paint': {
        'fill-color': match('class', { sand: p.sand }, p.grass),
        'fill-opacity': linear(
          [8, match('class', { grass: 0.45, wood: 0.6 }, 0.3)],
          [22, match('class', { grass: 0.45, wood: 1 }, 0.3)],
        ),
      },
    },
    {
      'id': `${sourceName}-water`,
      'type': 'fill',
      'source': sourceName,
      'source-layer': 'water',
      'filter': all(isPoly, neq('brunnel', 'tunnel')),
      'paint': {
        'fill-color': p.water,
        'fill-opacity': linear(
          mzFade(9, 10, WATER_OPACITY),
          mzFade(10, 11, WATER_OPACITY),
          mzFade(11, 12, WATER_OPACITY),
          mzFade(12, 13, WATER_OPACITY),
          mzFade(13, 14, WATER_OPACITY),
          [15, WATER_OPACITY],
        ),
      },
    },
    {
      'id': `${sourceName}-landcover-ice-shelf`,
      'type': 'fill',
      'source': sourceName,
      'source-layer': 'landcover',
      'filter': eq('subclass', 'ice_shelf'),
      'layout': { 'visibility': 'visible' },
      'paint': { 'fill-color': p.background, 'fill-opacity': 0.8 },
    },
    {
      'id': `${sourceName}-landcover-glacier`,
      'type': 'fill',
      'source': sourceName,
      'source-layer': 'landcover',
      'filter': eq('subclass', 'glacier'),
      'layout': { 'visibility': 'visible' },
      'paint': {
        'fill-color': p.backgroundAlt,
        'fill-opacity': stops(1, [0, 1], [8, 0.5]),
      },
    },
    {
      'id': `${sourceName}-landuse`,
      'type': 'fill',
      'source': sourceName,
      'source-layer': 'landcover',
      'filter': eq('class', 'farmland'),
      'layout': { 'visibility': 'visible' },
      'paint': { 'fill-color': p.farmland },
    },
    {
      'id': `${sourceName}-landuse_overlay_national_park`,
      'type': 'fill',
      'source': sourceName,
      'source-layer': 'park',
      'filter': isIn('class', 'national_park', 'nature_reserve'),
      'paint': {
        'fill-color': p.park,
        'fill-opacity': stops(1, [11, 0], [13, 0.75]),
      },
    },
    {
      'id': `${sourceName}-waterway-tunnel`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'waterway',
      'filter': all(isLine, eq('brunnel', 'tunnel')),
      'layout': { 'visibility': 'visible' },
      'paint': {
        'line-color': p.water,
        'line-dasharray': [3, 3],
        'line-gap-width': stops([12, 0], [20, 6]),
        'line-opacity': 1,
        'line-width': stops(1.4, [8, 1], [20, 2]),
      },
    },
    {
      'id': `${sourceName}-waterway`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'waterway',
      'filter': all(
        isLine,
        notIn('brunnel', 'tunnel', 'bridge'),
        neq('intermittent', 1),
      ),
      'layout': { 'visibility': 'visible' },
      'paint': waterLine(p.water),
    },
    {
      'id': `${sourceName}-waterway_intermittent`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'waterway',
      'filter': all(
        isLine,
        notIn('brunnel', 'tunnel', 'bridge'),
        eq('intermittent', 1),
      ),
      'layout': { 'visibility': 'visible' },
      'paint': { ...waterLine(p.water), 'line-dasharray': [2, 1] },
    },
    {
      'id': `${sourceName}-tunnel_railway_transit`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'transportation',
      'minzoom': 0,
      'filter': all(isLine, eq('brunnel', 'tunnel'), eq('class', 'transit')),
      'layout': { 'line-cap': 'butt', 'line-join': 'miter' },
      'paint': {
        'line-color': p.rail,
        'line-dasharray': [3, 3],
        'line-opacity': stops(1, [8, 0], [16, 1]),
      },
    },
    {
      'id': `${sourceName}-road_area_pier`,
      'type': 'fill',
      'metadata': {},
      'source': sourceName,
      'source-layer': 'transportation',
      'minzoom': 13,
      'filter': all(isPoly, eq('class', 'pier')),
      'layout': { 'visibility': 'visible' },
      'paint': { 'fill-antialias': true, 'fill-color': p.background },
    },
    {
      'id': `${sourceName}-road_pier`,
      'type': 'line',
      'metadata': {},
      'source': sourceName,
      'source-layer': 'transportation',
      'minzoom': 13,
      'filter': all(isLine, isIn('class', 'pier')),
      'layout': { 'line-cap': 'round', 'line-join': 'round' },
      'paint': {
        'line-color': p.background,
        'line-width': stops(1.2, [15, 1], [17, 4]),
      },
    },
    {
      'id': `${sourceName}-road_bridge_area`,
      'type': 'fill',
      'source': sourceName,
      'source-layer': 'transportation',
      'minzoom': 12,
      'filter': all(isPoly, isIn('brunnel', 'bridge')),
      'layout': {},
      'paint': { 'fill-color': p.background, 'fill-opacity': 0.5 },
    },
    {
      'id': `${sourceName}-road_path`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'transportation',
      'filter': all(isLine, isIn('class', 'path', 'track')),
      'layout': { 'line-cap': 'square', 'line-join': 'bevel' },
      'paint': {
        'line-color': p.path,
        'line-dasharray': [1, 1],
        'line-width': stops(1.55, [4, 0.25], [20, 10]),
      },
    },
    {
      'id': `${sourceName}-tunnel_road`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'transportation',
      'filter': all(
        isLine,
        eq('brunnel', 'tunnel'),
        isIn('class', ...BRIDGE_TUNNEL_ROAD),
      ),
      'layout': { 'line-cap': 'butt', 'line-join': 'miter' },
      'paint': {
        'line-color': match('class', { minor: p.roadMinorFill }, p.roadFill),
        'line-dasharray': [0.28, 0.14],
        'line-width': stops(1.4, [6, 0.5], [20, 30]),
      },
    },
    {
      'id': `${sourceName}-aeroway-area`,
      'type': 'fill',
      'metadata': { 'mapbox:group': '1444849345966.4436' },
      'source': sourceName,
      'source-layer': 'aeroway',
      'minzoom': 4,
      'filter': all(isPoly, isIn('class', 'runway', 'taxiway')),
      'layout': { 'visibility': 'visible' },
      'paint': {
        'fill-color': p.roadFill,
        'fill-opacity': stops(1, [13, 0], [14, 1]),
      },
    },
    {
      'id': `${sourceName}-aeroway-taxiway`,
      'type': 'line',
      'metadata': { 'mapbox:group': '1444849345966.4436' },
      'source': sourceName,
      'source-layer': 'aeroway',
      'minzoom': 12,
      'filter': all(isIn('class', 'taxiway'), isLine),
      'layout': {
        'line-cap': 'round',
        'line-join': 'round',
        'visibility': 'visible',
      },
      'paint': {
        'line-color': p.roadFill,
        'line-opacity': 1,
        'line-width': stops(1.5, [12, 1], [17, 10]),
      },
    },
    {
      'id': `${sourceName}-aeroway-runway`,
      'type': 'line',
      'metadata': { 'mapbox:group': '1444849345966.4436' },
      'source': sourceName,
      'source-layer': 'aeroway',
      'minzoom': 4,
      'filter': all(isIn('class', 'runway'), isLine),
      'layout': {
        'line-cap': 'round',
        'line-join': 'round',
        'visibility': 'visible',
      },
      'paint': {
        'line-color': p.roadFill,
        'line-opacity': 1,
        'line-width': stops(1.5, [11, 4], [17, 50]),
      },
    },
    // Roads — merged minor through motorway. All use white/near-white fill.
    // Per-class line-width controls visual hierarchy. Minor/service roads use
    // text-size 0 trick (line-width 0) below z13 to replicate the old minzoom.
    {
      'id': `${sourceName}-road`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'transportation',
      'filter': all(
        isLine,
        isIn('class', 'motorway', 'service', ...MINOR_ROAD),
        ['!has', 'brunnel'],
      ),
      'layout': { 'line-cap': 'round', 'line-join': 'round' },
      'paint': {
        'line-color': p.roadFill,
        // Fade-in window narrowed to z14-z15 (was z14-z16): at z16 the width
        // ramp below has already caught up to a normal visible thickness, so
        // a fade stretched all the way to z16 left minor/service roads both
        // near their heaviest width *and* still partly transparent for a
        // wide range — a thick, washed-out look right around z15.5-15.9.
        'line-opacity': linear(
          [13, match('class', { tertiary: 0, minor: 0, service: 0 }, 1)],
          [14, match('class', { minor: 0, service: 0 }, 1)],
          [15, 1],
        ),
        'line-width': exp(
          1.4,
          [
            6,
            match('class', {
              motorway: 0.5,
              trunk: 0.5,
              primary: 0.5,
              secondary: 0.5,
              tertiary: 0.5,
            }, 0),
          ],
          [8, match('class', { motorway: 1 }, 0.5)],
          [
            11,
            match('class', {
              motorway: 3,
              trunk: 2,
              primary: 2,
              secondary: 1.5,
              tertiary: 1.5,
            }, 0.5),
          ],
          [
            13,
            match('class', {
              motorway: 6,
              trunk: 4,
              primary: 4,
              secondary: 3,
              tertiary: 3,
              minor: 1,
              service: 1,
            }, 2.5),
          ],
          [
            16,
            match('class', {
              motorway: 10,
              trunk: 14,
              primary: 14,
              secondary: 10,
              tertiary: 10,
              // Explicitly thinner than tertiary — previously minor/service
              // fell through to the 12/30 "default" below, making residential
              // streets render *wider* than tertiary roads at high zoom.
              minor: 6,
              service: 6,
            }, 12),
          ],
          [
            20,
            match('class', {
              motorway: 10,
              secondary: 20,
              tertiary: 20,
              minor: 14,
              service: 14,
            }, 30),
          ],
        ),
      },
    },
    {
      'id': `${sourceName}-railway`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'transportation',
      'filter': isIn('class', 'rail', 'transit'),
      'paint': {
        'line-color': p.rail,
        'line-opacity': linear(
          [8, 0],
          mzFade(8, 9, 0.125),
          mzFade(9, 10, 0.25),
          mzFade(10, 11, 0.375),
          mzFade(11, 12, 0.5),
          mzFade(12, 13, 0.625),
          mzFade(13, 14, 0.75),
          mzFade(14, 15, 0.875),
          [16, 1],
        ),
      },
    },
    // ── Transit route overlay — colour/name/ref pulled from OSM route
    // relations (see process.lua's `NextRelation`/`FindInRelation` pass).
    // Drawn on top of the plain `railway` line above; toggled as a group by
    // `isTransitLayer()` from the map menu, independent of the base track.
    //
    // One treatment for every rail-family mode (metro and long-distance
    // alike): thin, in the route's real colour, with a casing underneath
    // (same case-then-line technique as `bridge_case`/`bridge`) that matches
    // the basemap background rather than a fixed white, so it still reads as
    // a separator between overlapping lines under the dark palette.
    {
      'id': `${sourceName}-transit-route-case`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'transportation',
      'filter': has('is_route'),
      'layout': { 'line-cap': 'round', 'line-join': 'round' },
      'paint': {
        'line-color': p.transitCase,
        'line-width': stops(1.3, [10, 2.5], [16, 5]),
      },
    },
    {
      'id': `${sourceName}-transit-route`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'transportation',
      'filter': has('is_route'),
      'layout': { 'line-cap': 'round', 'line-join': 'round' },
      'paint': {
        'line-color': coalesce(
          get('route_colour'),
          match('subclass', {
            subway: p.transitSubway,
            tram: p.transitTram,
            light_rail: p.transitLightRail,
            rail: p.transitTrain,
            narrow_gauge: p.transitTrain,
            monorail: p.transitMonorail,
            funicular: p.transitMonorail,
          }, p.rail),
        ),
        'line-width': stops(1.3, [10, 1], [16, 2.5]),
      },
    },
    {
      'id': `${sourceName}-transit-route-label`,
      'type': 'symbol',
      'source': sourceName,
      'source-layer': 'transportation',
      'minzoom': 12,
      'filter': all(has('is_route'), has('route_ref')),
      'layout': {
        'symbol-placement': 'line',
        'text-field': '{route_ref}',
        'text-font': ['Noto Sans Medium'],
        'text-size': 11,
      },
      'paint': {
        'text-color': coalesce(get('route_colour'), p.rail),
        'text-halo-color': p.transitCase,
        'text-halo-width': 1.5,
      },
    },
    {
      'id': `${sourceName}-waterway-bridge-case`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'waterway',
      'filter': all(isLine, eq('brunnel', 'bridge')),
      'layout': { 'line-cap': 'butt', 'line-join': 'miter' },
      'paint': {
        'line-color': p.waterwayBridgeCasing,
        'line-gap-width': stops(1.55, [4, 0.25], [20, 30]),
        'line-width': stops(1.6, [12, 0.5], [20, 10]),
      },
    },
    {
      'id': `${sourceName}-waterway-bridge`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'waterway',
      'filter': all(isLine, eq('brunnel', 'bridge')),
      'layout': { 'line-cap': 'round', 'line-join': 'round' },
      'paint': {
        'line-color': p.water,
        'line-width': stops(1.55, [4, 0.25], [20, 30]),
      },
    },
    {
      'id': `${sourceName}-bridge_case`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'transportation',
      'filter': all(
        isLine,
        eq('brunnel', 'bridge'),
        isIn('class', ...BRIDGE_TUNNEL_ROAD),
      ),
      'layout': { 'line-cap': 'butt', 'line-join': 'miter' },
      'paint': {
        'line-color': p.bridgeCasing,
        'line-gap-width': stops(1.55, [4, 0.25], [20, 30]),
        'line-opacity': linear(
          [13, match('class', { tertiary: 0, minor: 0 }, 1)],
          [14, match('class', { minor: 0 }, 1)],
          [16, 1],
        ),
        'line-width': stops(1.6, [12, 0.5], [20, 10]),
      },
    },
    {
      'id': `${sourceName}-bridge`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'transportation',
      'filter': all(
        isLine,
        eq('brunnel', 'bridge'),
        isIn('class', ...BRIDGE_TUNNEL_ROAD),
      ),
      'layout': { 'line-cap': 'round', 'line-join': 'round' },
      'paint': {
        'line-color': match('class', { minor: p.roadMinorFill }, p.roadFill),
        'line-opacity': linear(
          [13, match('class', { tertiary: 0, minor: 0 }, 1)],
          [14, match('class', { minor: 0 }, 1)],
          [16, 1],
        ),
        'line-width': stops(1.4, [6, 0.5], [20, 30]),
      },
    },
    {
      'id': `${sourceName}-admin_sub`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'boundary',
      'filter': isIn('admin_level', 4, 6, 8),
      'layout': { 'visibility': 'visible' },
      'paint': {
        'line-color': p.boundarySub,
        'line-dasharray': [2, 1],
      },
    },
    // Country boundaries — world tiles cover z0-5 so regional tiles only
    // need to render from z5 onwards.
    {
      'id': `${sourceName}-admin_country`,
      'type': 'line',
      'source': sourceName,
      'source-layer': 'boundary',
      'minzoom': 5,
      'filter': all(lte('admin_level', 2), isLine),
      'layout': { 'line-cap': 'round', 'line-join': 'round' },
      'paint': {
        'line-color': p.boundaryCountry,
        'line-width': stops(1.3, [3, 0.5], [22, 15]),
      },
    },
    // Station icons/labels render after every line layer (including road and
    // waterway bridges) so an elevated highway or bridge deck never paints
    // over a station marker sitting beneath it.
    {
      'id': `${sourceName}-transit-station`,
      'type': 'symbol',
      'source': sourceName,
      'source-layer': 'poi',
      'minzoom': 13,
      'filter': all(
        eq('class', 'railway'),
        isIn('subclass', 'station', 'halt', 'tram_stop'),
      ),
      'layout': {
        'icon-image': stationIconExpression(),
        'icon-allow-overlap': true,
      },
    },
    {
      'id': `${sourceName}-transit-station-label`,
      'type': 'symbol',
      'source': sourceName,
      'source-layer': 'poi',
      'minzoom': 14,
      'filter': all(
        eq('class', 'railway'),
        isIn('subclass', 'station', 'halt', 'tram_stop'),
      ),
      'layout': {
        'text-anchor': 'top',
        'text-field': '{name:latin}',
        'text-font': FONT_REG,
        'text-offset': [0, 1],
        'text-size': 11,
      },
      'paint': {
        'text-color': p.textStation,
        'text-halo-color': p.haloStrong,
        'text-halo-width': 1.5,
      },
    },
    {
      'id': `${sourceName}-poi_label`,
      'type': 'symbol',
      'source': sourceName,
      'source-layer': 'poi',
      'minzoom': 15,
      'filter': all(isPoint, eq('rank', 1)),
      'layout': {
        'text-anchor': 'top',
        'text-field': '{name:latin}',
        'text-font': ['Noto Sans Regular'],
        'text-max-width': 8,
        'text-offset': [0, 0.5],
        'text-size': 11,
        'visibility': 'visible',
      },
      'paint': {
        'text-color': p.textPoi,
        'text-halo-blur': 1,
        'text-halo-color': p.halo75,
        'text-halo-width': 1,
      },
    },
    {
      'id': `${sourceName}-airport-label`,
      'type': 'symbol',
      'source': sourceName,
      'source-layer': 'aerodrome_label',
      'minzoom': 11,
      'filter': has('iata'),
      'layout': {
        'text-anchor': 'center',
        'text-field': '{name:latin}',
        'text-font': ['Noto Sans Regular'],
        'text-max-width': 8,
        'text-size': 11,
        'visibility': 'visible',
      },
      'paint': {
        'text-color': p.textPoi,
        'text-halo-blur': 1,
        'text-halo-color': p.halo75,
        'text-halo-width': 1,
      },
    },
    {
      'id': `${sourceName}-road_major_label`,
      'type': 'symbol',
      'source': sourceName,
      'source-layer': 'transportation_name',
      'minzoom': 14,
      'filter': isLine,
      'layout': {
        'symbol-placement': 'line',
        'text-field': '{name:latin}',
        'text-font': ['Noto Sans Regular'],
        'text-letter-spacing': 0.05,
        'text-rotation-alignment': 'map',
        'text-size': stops(1, [13, 10], [16, 12]),
        'visibility': 'visible',
      },
      'paint': {
        'text-color': p.textRoad,
        'text-halo-color': p.haloStrong,
        'text-halo-width': 1.5,
      },
    },
    // Merged place labels — single collision detection pass for cities,
    // towns, and suburbs. Per-class zoom visibility is controlled via
    // text-size (0 = hidden) so features only appear at their intended zoom.
    {
      'id': `${sourceName}-place_label`,
      'type': 'symbol',
      'source': sourceName,
      'source-layer': 'place',
      'minzoom': 11,
      'maxzoom': 16,
      'filter': all(
        isPoint,
        isIn('class', 'city', 'town', 'suburb', 'neighbourhood', 'quarter'),
      ),
      'layout': {
        'text-field': '{name:latin}',
        'text-font': cond(['==', get('class'), 'city'], FONT_MED, FONT_REG),
        'text-max-width': match('class', { city: 10, town: 8 }, 6),
        'text-size': linear(
          [11, match('class', { city: 11 }, 0)],
          [12, match('class', { city: 12, town: 10 }, 0)],
          [13, match('class', { city: 13.5, town: 11 }, 10)],
          [15, match('class', { city: 15, town: 13 }, 11)],
          [16, match('class', { city: 15, town: 0 }, 12)],
        ),
        'text-transform': match('class', {
          suburb: 'uppercase',
          neighbourhood: 'uppercase',
          quarter: 'uppercase',
        }, 'none'),
        'text-letter-spacing': match('class', {
          suburb: 0.05,
          neighbourhood: 0.05,
          quarter: 0.05,
        }, 0),
        'symbol-sort-key': coalesce(get('rank'), 10),
        'text-allow-overlap': false,
      },
      'paint': {
        'text-color': match(
          'class',
          { city: p.textCity, town: p.textTown },
          p.textOther,
        ),
        'text-halo-blur': 0,
        'text-halo-color': match(
          'class',
          { city: p.halo75 },
          p.haloStrong,
        ),
        'text-halo-width': match('class', { city: 2, town: 1.5 }, 1),
      },
    },
    {
      'id': `${sourceName}-country_label`,
      'type': 'symbol',
      'source': sourceName,
      'source-layer': 'place',
      'minzoom': 7,
      'maxzoom': 12,
      'filter': all(isPoint, eq('class', 'country')),
      'layout': {
        'text-field': '{name:latin}',
        'text-font': cond(has('iso_a2'), FONT_MED, FONT_REG),
        'text-max-width': 10,
        'text-size': stops([3, 12], [8, 22]),
        'visibility': 'visible',
      },
      'paint': {
        'text-color': p.textCountry,
        'text-halo-blur': 0,
        'text-halo-color': p.halo75,
        'text-halo-width': 2,
      },
    },
  ]
}

const TRANSIT_LAYER_SUFFIXES = [
  '-transit-route-case',
  '-transit-route',
  '-transit-route-label',
  '-transit-station',
  '-transit-station-label',
]

/** Whether a style layer id belongs to the transit route/station overlay. */
export function isTransitLayer(layerId: string): boolean {
  return TRANSIT_LAYER_SUFFIXES.some((suffix) => layerId.endsWith(suffix))
}

/**
 * MapLibre layer definitions for the OSM-based world basemap tiles.
 *
 * Source-layers emitted by tilemaker + process.world.lua / config.world.json:
 *   water        — ocean polygons (from coastline shapefile) + inland water
 *   landcover    — wood, wetland, sand, farmland, ice, rock, grass
 *   landuse      — school, hospital, military, residential, commercial, etc.
 *   park         — national_park, nature_reserve
 *   boundary     — administrative boundaries (admin_level 2-6)
 *   waterway     — named rivers
 *   place        — continent, country, state, city, town, village labels
 *   mountain_peak        — peaks and volcanoes
 *
 * Transportation is intentionally omitted — roads are distracting at world
 * zoom and are available in regional tiles once downloaded.
 *
 * Colors are harmonised with the palette in `buildPalette` so the visual
 * transition when a regional tile loads is as seamless as possible, in
 * either theme.
 */
export function buildWorldLayers(dark: boolean) {
  const p = buildPalette(dark)
  return [
    // ── Landcover — renders below water so coastline-overlapping polygons are masked
    {
      'id': 'landcover',
      'type': 'fill',
      'source': 'world',
      'source-layer': 'landcover',
      'filter': includes('class', [
        'grass',
        'wood',
        'wetland',
        'sand',
        'rock',
        'ice',
      ]),
      'paint': {
        'fill-color': match('class', {
          grass: p.grass,
          wood: p.grass,
          wetland: p.wetland,
          sand: p.sand,
          rock: p.rock,
          ice: p.backgroundAlt,
        }, p.grass),
        'fill-opacity': linear(
          [
            0,
            match('class', { ice: 1, wood: 0.6, sand: 0.3, grass: 0.45 }, 0.5),
          ],
          [
            6,
            match(
              'class',
              { ice: 0.75, wood: 0.6, sand: 0.3, grass: 0.45 },
              0.5,
            ),
          ],
          [
            9,
            match(
              'class',
              { ice: 0.5, wood: 0.8, sand: 0.3, grass: 0.45 },
              0.5,
            ),
          ],
        ),
      },
    },

    // ── Ocean / Water (fill) — above landcover to mask OSM polygons that extend past coastlines
    {
      'id': 'water',
      'type': 'fill',
      'source': 'world',
      'source-layer': 'water',
      'paint': { 'fill-color': p.water },
    },

    // ── Parks / nature reserves ───────────────────────────────────────────────
    {
      'id': 'park',
      'type': 'fill',
      'source': 'world',
      'source-layer': 'park',
      'paint': {
        'fill-color': p.park,
        'fill-opacity': stops(1, [3, 0], [7, 0.75]),
      },
    },

    // ── Waterways ─────────────────────────────────────────────────────────────
    {
      'id': 'waterway',
      'type': 'line',
      'source': 'world',
      'source-layer': 'waterway',
      'minzoom': 6,
      'paint': {
        'line-color': p.water,
        'line-opacity': 1,
        'line-width': stops(1.4, [6, 0.5], [9, 3]),
      },
    },

    // ── Boundaries ────────────────────────────────────────────────────────────
    {
      'id': 'boundary-state',
      'type': 'line',
      'source': 'world',
      'source-layer': 'boundary',
      'filter': all(gte('admin_level', 3), lte('admin_level', 6)),
      'minzoom': 3,
      'paint': {
        'line-color': p.boundarySub,
        'line-width': 0.5,
        'line-dasharray': [2, 1],
      },
    },
    {
      'id': 'boundary-country',
      'type': 'line',
      'source': 'world',
      'source-layer': 'boundary',
      'filter': eq('admin_level', 2),
      'paint': {
        'line-color': p.boundaryCountry,
        'line-width': stops(1.3, [0, 0.5], [4, 1], [7, 1.5]),
      },
    },

    // ── Place labels ──────────────────────────────────────────────────────────
    {
      'id': 'place-continent',
      'type': 'symbol',
      'source': 'world',
      'source-layer': 'place',
      'filter': eq('class', 'continent'),
      'maxzoom': 3,
      'layout': {
        'text-field': coalesce(get('name:latin'), get('name')),
        'text-font': ['Noto Sans Medium'],
        'text-size': 13,
        'text-transform': 'uppercase',
        'text-letter-spacing': 0.1,
      },
      'paint': {
        'text-color': p.textContinent,
        'text-halo-color': p.halo70,
        'text-halo-width': 1,
      },
    },
    {
      'id': 'place-country',
      'type': 'symbol',
      'source': 'world',
      'source-layer': 'place',
      'filter': eq('class', 'country'),
      'maxzoom': 7,
      'layout': {
        'text-field': coalesce(get('name:latin'), get('name')),
        'text-font': cond(has('iso_a2'), FONT_MED, FONT_REG),
        'text-max-width': 10,
        'text-size': stops([3, 12], [8, 22]),
      },
      'paint': {
        'text-color': p.textCountry,
        'text-halo-blur': 0,
        'text-halo-color': p.halo75,
        'text-halo-width': 2,
      },
    },
    {
      'id': 'place-state',
      'type': 'symbol',
      'source': 'world',
      'source-layer': 'place',
      'filter': isIn('class', 'state', 'province'),
      'minzoom': 4,
      'maxzoom': 8,
      'layout': {
        'text-field': coalesce(get('name:latin'), get('name')),
        'text-font': ['Noto Sans Regular'],
        'text-size': 10,
        'text-transform': 'uppercase',
        'text-letter-spacing': 0.05,
        'text-max-width': 6,
      },
      'paint': {
        'text-color': p.textState,
        'text-halo-color': p.halo60,
        'text-halo-width': 1,
        'text-opacity': 0.8,
      },
    },
    {
      'id': 'place-settlement',
      'type': 'symbol',
      'source': 'world',
      'source-layer': 'place',
      'filter': includes('class', ['city', 'town', 'village']),
      'minzoom': 6,
      'maxzoom': 11,
      'layout': {
        'text-field': coalesce(get('name:latin'), get('name')),
        'text-font': ['Noto Sans Regular'],
        'text-max-width': match('class', { city: 10 }, 6),
        'text-size': linear(
          [6, match('class', { city: 11, town: 10 }, 0)],
          [8, match('class', { city: 12, town: 11 }, 10)],
          [9, match('class', { city: 15, town: 14 }, 14)],
        ),
      },
      'paint': {
        'text-color': match('class', { city: p.textCity }, p.textSettlement),
        'text-halo-blur': 0,
        'text-halo-color': p.halo75,
        'text-halo-width': 2,
      },
    },
  ]
}
