// TimezonePicker: a drop-down of time zones grouped by region, each with its UTC
// offset ("(UTC+01:00) Paris"). It is the browser's own <select>, so a plain <form>
// sends the IANA name ("Europe/Paris") as `name`, and it works with the keyboard.
//
//   <TimezonePicker name="tz" label="Time zone" value="Europe/Lisbon"/>
//   <TimezonePicker name="tz" label="Time zone" zones={["UTC", "Europe/London", "America/New_York"]}/>
//
// value: the zone chosen to start with (an IANA name). zones: the names to offer,
// in place of the built-in list of about a hundred; a region is the part before
// the slash. at: the date-time the offsets are worked out for (now by default), so
// daylight saving is right. placeholder: a first line that cannot be chosen.
// hint, error, required, disabled, field (the id, default "timezone-picker-" +
// name) and size ("sm" or "md") as in Select.
// on_change(zone): inside one of your components, given the IANA name chosen.

import ui/theme.qwe use ui_cls

let timezone_picker_css = css`
.ui-timezone-picker { display: grid; gap: 6px; min-width: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }
.ui-timezone-picker-label { font-size: 13px; font-weight: 500; color: var(--ui-fg); }
.ui-timezone-picker-req { margin-left: 2px; color: var(--ui-danger); }
.ui-timezone-picker-box { position: relative; display: block; }
.ui-timezone-picker-box::after {
  content: ""; position: absolute; right: 14px; top: 50%; width: 7px; height: 7px; margin-top: -6px;
  border: solid var(--ui-muted); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg); pointer-events: none;
}
.ui-timezone-picker select {
  appearance: none; display: block; width: 100%; height: 38px; padding: 0 36px 0 12px;
  background: var(--ui-surface); color: var(--ui-fg); font: 14px var(--ui-font); font-variant-numeric: tabular-nums;
  border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); cursor: pointer;
  text-overflow: ellipsis; transition: border-color .15s, box-shadow .15s;
}
.ui-timezone-picker select:hover { border-color: var(--ui-strong); }
.ui-timezone-picker select:focus-visible { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }
.ui-timezone-picker select:has(option[value=""]:checked) { color: var(--ui-muted); }
.ui-timezone-picker option { color: var(--ui-fg); background: var(--ui-surface); }
.ui-timezone-picker-sm select { height: 30px; padding: 0 30px 0 10px; font-size: 13px; }
.ui-timezone-picker-sm .ui-timezone-picker-box::after { right: 11px; width: 6px; height: 6px; margin-top: -5px; }
.ui-timezone-picker-invalid select, .ui-timezone-picker-invalid select:hover { border-color: var(--ui-danger); }
.ui-timezone-picker-hint, .ui-timezone-picker-error { margin: 0; font-size: 13px; color: var(--ui-muted); }
.ui-timezone-picker-error { color: var(--ui-danger); }
.ui-timezone-picker-disabled { opacity: .55; }
.ui-timezone-picker-disabled select { cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) { .ui-timezone-picker select { transition: none; } }
`

let timezone_picker_zones = [
    "UTC",
    "Africa/Abidjan", "Africa/Accra", "Africa/Addis_Ababa", "Africa/Algiers", "Africa/Cairo", "Africa/Casablanca",
    "Africa/Dar_es_Salaam", "Africa/Johannesburg", "Africa/Khartoum", "Africa/Kinshasa", "Africa/Lagos", "Africa/Nairobi",
    "Africa/Tunis",
    "America/Anchorage", "America/Argentina/Buenos_Aires", "America/Asuncion", "America/Bogota", "America/Caracas",
    "America/Chicago", "America/Denver", "America/Guatemala", "America/Halifax", "America/Havana", "America/La_Paz",
    "America/Lima", "America/Los_Angeles", "America/Mexico_City", "America/Montevideo", "America/New_York",
    "America/Panama", "America/Phoenix", "America/Santiago", "America/Sao_Paulo", "America/St_Johns", "America/Toronto",
    "America/Vancouver",
    "Asia/Almaty", "Asia/Baghdad", "Asia/Baku", "Asia/Bangkok", "Asia/Beirut", "Asia/Colombo", "Asia/Dhaka", "Asia/Dubai",
    "Asia/Hong_Kong", "Asia/Jakarta", "Asia/Jerusalem", "Asia/Kabul", "Asia/Karachi", "Asia/Kathmandu", "Asia/Kolkata",
    "Asia/Kuala_Lumpur", "Asia/Manila", "Asia/Riyadh", "Asia/Seoul", "Asia/Shanghai", "Asia/Singapore", "Asia/Taipei",
    "Asia/Tashkent", "Asia/Tbilisi", "Asia/Tehran", "Asia/Tokyo", "Asia/Yangon", "Asia/Yerevan",
    "Atlantic/Azores", "Atlantic/Cape_Verde", "Atlantic/Reykjavik",
    "Australia/Adelaide", "Australia/Brisbane", "Australia/Darwin", "Australia/Hobart", "Australia/Melbourne",
    "Australia/Perth", "Australia/Sydney",
    "Europe/Amsterdam", "Europe/Athens", "Europe/Belgrade", "Europe/Berlin", "Europe/Brussels", "Europe/Bucharest",
    "Europe/Budapest", "Europe/Copenhagen", "Europe/Dublin", "Europe/Helsinki", "Europe/Istanbul", "Europe/Kyiv",
    "Europe/Lisbon", "Europe/London", "Europe/Madrid", "Europe/Moscow", "Europe/Oslo", "Europe/Paris", "Europe/Prague",
    "Europe/Rome", "Europe/Stockholm", "Europe/Vienna", "Europe/Warsaw", "Europe/Zurich",
    "Indian/Maldives", "Indian/Mauritius",
    "Pacific/Auckland", "Pacific/Fiji", "Pacific/Guam", "Pacific/Honolulu", "Pacific/Port_Moresby", "Pacific/Tongatapu",
]

// "+01:00" for a zone at the instant `at` ("+00:00" for UTC).
fn timezone_picker_offset(zone: str, at: str) -> str {
    let t = date.to_zone(at, zone)
    if is_err(t) || t == null { return "+00:00" }
    let tail = slice(t, len(t) - 6, len(t))
    ends_with(t, "Z") ? "+00:00" : tail
}

// "Buenos Aires" from "America/Argentina/Buenos_Aires".
fn timezone_picker_city(zone: str) -> str = replace(last(split(zone, "/")), "_", " ")

// Minutes from UTC, from "+05:30", for sorting.
fn timezone_picker_minutes(offset: str) -> int {
    let sign = starts_with(offset, "-") ? -1 : 1
    sign * ((int(slice(offset, 1, 3)) ?? 0) * 60 + (int(slice(offset, 4, 6)) ?? 0))
}

view TimezonePicker(name: str? = null, label: str? = null, value: str? = null, zones: list? = null, at: str? = null,
                    placeholder: str? = null, hint: str? = null, error: str? = null, required: bool = false,
                    disabled: bool = false, field: str? = null, size: str = "md", on_change = null, class: str? = null) {
    style global timezone_picker_css
    let id = field ?? `timezone-picker-${name ?? "zone"}`
    let described = ui_cls([hint != null && `${id}-hint`, error != null && `${id}-error`])
    let when = at ?? date.now()
    let rows = (zones ?? timezone_picker_zones) | map((z) => {
        let off = timezone_picker_offset(z, when)
        { zone: z, region: contains(z, "/") ? first(split(z, "/")) : "Other", city: timezone_picker_city(z),
          offset: off, minutes: timezone_picker_minutes(off) }
    })
    let regions = rows | map((r) => r.region) | unique
    let opt = (r) => <option value={r.zone} selected={r.zone == value}>{`(UTC${r.offset}) ${r.city}`}</option>
    <div class={ui_cls(["ui-timezone-picker", size == "sm" && "ui-timezone-picker-sm", error != null && "ui-timezone-picker-invalid", disabled && "ui-timezone-picker-disabled", class])}>
        {label != null && <label.ui-timezone-picker-label for={id}>
            {label}{required && <span.ui-timezone-picker-req aria-hidden="true">*</span>}
        </label>}
        <span.ui-timezone-picker-box>
            <select id={id} name={name} required={required} disabled={disabled}
                    aria-invalid={error != null ? "true" : null} aria-describedby={described != "" ? described : null}
                    @change={(e) => { on_change && on_change(e.value) }}>
                {placeholder != null && <option value="" disabled selected={value == null}>{placeholder}</option>}
                {for g in regions => {
                    let mine = rows | filter((r) => r.region == g) | sort_by((r) => r.city) | sort_by((r) => r.minutes)
                    g == "Other" ? (mine | map(opt)) : <optgroup label={g}>{for r in mine => opt(r)}</optgroup>
                }}
            </select>
        </span>
        {hint != null && <p.ui-timezone-picker-hint id={`${id}-hint`}>{hint}</p>}
        {error != null && <p.ui-timezone-picker-error id={`${id}-error`} role="alert">{error}</p>}
    </div>
}
