// ChecklistInput: a list of checkboxes with a progress line over them that says
// how many are done ("3 of 5 done") and fills as they are ticked. They are real
// checkboxes, so a plain <form> sends each ticked value as `name`.
//
//   <ChecklistInput name="setup" label="Getting started"
//                   items={["Create a project", { label: "Invite the team", value: "invite", checked: true }, "Deploy"]}/>
//   <ChecklistInput name="todo" items={tasks} on_change={(done) => { done_ids = done }}/>
//
// items: strings (the text is the value), or maps of { label, value, checked?,
// hint?, disabled? }. values: the values ticked to start with (as well as each
// item's own checked). label: a heading for the list. done_text: what the line
// says when everything is ticked ("All done!"). strike (true): ticked items are
// struck through. hint and error (a line under the list) and field (the ids'
// start, default "checklist-input-" + name) as in CheckboxGroup.
// on_change(values): inside one of your components, called with the values
// ticked, in the items' order, after each change.

import ui/theme.qwe use ui_cls

let checklist_input_css = css`
.ui-checklist-input { display: grid; gap: 10px; min-width: 0; margin: 0; padding: 0; border: 0; font: 14px/1.4 var(--ui-font); color: var(--ui-fg); }
.ui-checklist-input-legend { padding: 0; font-size: 13px; font-weight: 500; }
.ui-checklist-input-progress { display: grid; gap: 6px; }
.ui-checklist-input-count { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--ui-muted); font-variant-numeric: tabular-nums; }
.ui-checklist-input-all { color: var(--ui-success); font-weight: 500; }
.ui-checklist-input-bar { height: 6px; border-radius: 3px; background: var(--ui-subtle); overflow: hidden; }
.ui-checklist-input-bar span { display: block; height: 100%; width: var(--p); border-radius: 3px; background: var(--ui-accent); transition: width .25s, background .25s; }
.ui-checklist-input-done .ui-checklist-input-bar span { background: var(--ui-success); }
.ui-checklist-input-list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
.ui-checklist-input-item { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; width: fit-content; }
.ui-checklist-input-item input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.ui-checklist-input-box {
  flex: none; position: relative; width: 18px; height: 18px; margin-top: 1px; background: var(--ui-surface);
  border: 1px solid var(--ui-strong); border-radius: 4px; transition: background .15s, border-color .15s, box-shadow .15s;
}
.ui-checklist-input-box::after {
  content: ""; position: absolute; left: 5px; top: 1.5px; width: 5px; height: 10px; border: solid var(--ui-accent-fg);
  border-width: 0 2px 2px 0; transform: rotate(45deg) scale(0); transition: transform .12s;
}
.ui-checklist-input-item:hover .ui-checklist-input-box { border-color: var(--ui-accent); }
.ui-checklist-input-item input:checked + .ui-checklist-input-box { background: var(--ui-accent); border-color: var(--ui-accent); }
.ui-checklist-input-item input:checked + .ui-checklist-input-box::after { transform: rotate(45deg) scale(1); }
.ui-checklist-input-item input:focus-visible + .ui-checklist-input-box { box-shadow: var(--ui-focus); }
.ui-checklist-input-text { display: grid; gap: 2px; }
.ui-checklist-input-strike input:checked ~ .ui-checklist-input-text > :first-child { text-decoration: line-through; color: var(--ui-muted); }
.ui-checklist-input-hint-item { font-size: 13px; color: var(--ui-muted); }
.ui-checklist-input-item:has(input:disabled) { opacity: .55; cursor: not-allowed; }
.ui-checklist-input-hint, .ui-checklist-input-error { margin: 0; font-size: 13px; color: var(--ui-muted); }
.ui-checklist-input-error { color: var(--ui-danger); }
@media (prefers-reduced-motion: reduce) { .ui-checklist-input-bar span, .ui-checklist-input-box, .ui-checklist-input-box::after { transition: none; } }
`

// An item as { label, value, checked, hint, disabled }, from a string or a map.
fn checklist_input_item(it) = type_of(it) == "map"
    ? { label: it.label ?? str(it.value), value: str(it.value ?? it.label), checked: it.checked ?? false, hint: it.hint, disabled: it.disabled ?? false }
    : { label: str(it), value: str(it), checked: false, hint: null, disabled: false }

component ChecklistInput(name: str, items: list = [], values: list = [], label: str? = null, done_text: str = "All done!",
                         strike: bool = true, hint: str? = null, error: str? = null, field: str? = null, on_change = null,
                         class: str? = null) {
    style global checklist_input_css
    let list = items | map(checklist_input_item)
    state ticked = list | filter((it) => it.checked || contains(values | map((v) => str(v)), it.value)) | map((it) => it.value)
    let id = field ?? `checklist-input-${name}`
    let total = len(list)
    let done = len(list | filter((it) => contains(ticked, it.value)))
    let pct = total == 0 ? 0 : round(done * 100 / total)
    let described = ui_cls([`${id}-count`, hint != null && `${id}-hint`, error != null && `${id}-error`])
    <fieldset class={ui_cls(["ui-checklist-input", done == total && total > 0 && "ui-checklist-input-done", strike && "ui-checklist-input-strike", class])}
              id={id} aria-describedby={described} aria-invalid={error != null ? "true" : null}>
        {label != null && <legend.ui-checklist-input-legend>{label}</legend>}
        <div.ui-checklist-input-progress>
            <div.ui-checklist-input-count id={`${id}-count`} aria-live="polite">
                <span>{done} of {total} done</span>
                {done == total && total > 0 ? <span.ui-checklist-input-all>{done_text}</span> : <span>{pct}%</span>}
            </div>
            <div.ui-checklist-input-bar role="progressbar" aria-label={label ?? "Progress"} aria-valuemin="0" aria-valuemax={total} aria-valuenow={done}
                 aria-valuetext={`${done} of ${total} done`}>
                <span style={`--p: ${pct}%`}></span>
            </div>
        </div>
        <ul.ui-checklist-input-list>
            {for [i, it] in enumerate(list) => <li key={it.value}>
                <label.ui-checklist-input-item for={`${id}-${i}`}>
                    <input type="checkbox" id={`${id}-${i}`} name={name} value={it.value} checked={contains(ticked, it.value)}
                           disabled={it.disabled} aria-describedby={it.hint != null ? `${id}-${i}-hint` : null}
                           @change={(e) => {
                               ticked = e.checked ? list | filter((x) => contains(ticked, x.value) || x.value == it.value) | map((x) => x.value)
                                                  : ticked | filter((v) => v != it.value)
                               on_change && on_change(ticked)
                           }}/>
                    <span.ui-checklist-input-box aria-hidden="true"></span>
                    <span.ui-checklist-input-text>
                        <span>{it.label}</span>
                        {it.hint != null && <span.ui-checklist-input-hint-item id={`${id}-${i}-hint`}>{it.hint}</span>}
                    </span>
                </label>
            </li>}
        </ul>
        {hint != null && <p.ui-checklist-input-hint id={`${id}-hint`}>{hint}</p>}
        {error != null && <p.ui-checklist-input-error id={`${id}-error`} role="alert">{error}</p>}
    </fieldset>
}
