// datetime is Schritt 7 (continued) of the module-system migration. Read-mode
// display is byte-identical to text-cell (a date value is already a plain
// ISO-ish string by the time it reaches here) -- the real value-add is
// edit mode: a native <input type="datetime-local"> that commits on "change", not
// Enter. DataGrid.svelte's own generic input needed an explicit confirm
// button for date/datetime specifically, because Chromium's native date
// picker dispatches a keydown with a blank key/code on Enter, indistinguishable
// from the arrow keys used to adjust segments -- see that button's comment
// in DataGrid.svelte. Committing on the input's own "change" event sidesteps
// the problem entirely instead of working around it, so this needs no
// confirm button at all.
function formatValue(value) {
	if (value === null || value === undefined) return '';
	if (typeof value === 'boolean') return value ? 'true' : 'false';
	if (typeof value === 'object') return JSON.stringify(value);
	return String(value);
}

customElements.define(
	'datetime-cell',
	class extends HTMLElement {
		static get observedAttributes() {
			return ['value'];
		}
		connectedCallback() {
			this.render();
		}
		attributeChangedCallback() {
			this.render();
		}
		render() {
			let raw = null;
			try {
				raw = JSON.parse(this.getAttribute('value') ?? 'null');
			} catch {
				raw = null;
			}
			this.textContent = formatValue(raw);
		}
	}
);

customElements.define(
	'datetime-edit',
	class extends HTMLElement {
		// See text-edit's doc comment for the full root-cause writeup (same
		// bug, same fix, found live via the draft-row editor): this class had
		// no way to react to "value" being reset from outside after the
		// element stayed mounted, so a successful draft-row save silently
		// left the picker showing a stale datetime while the real state was
		// empty, and the next save submitted that as blank.
		static get observedAttributes() {
			return ['value'];
		}
		attributeChangedCallback(name, oldValue, newValue) {
			if (this._input && this._input.value !== (newValue ?? '')) {
				this._input.value = newValue ?? '';
			}
		}
		connectedCallback() {
			this._committed = false;
			const input = document.createElement('input');
			input.type = 'datetime-local';
			input.step = '1';
			// w-full, not flex-1/min-w-0: see text-edit's doc comment -- this
			// isn't a flex child of anything, it's a plain child of this
			// custom element, which is the actual flex/block item
			// DataGrid.svelte sizes.
			input.className = 'w-full border-2 border-brand-500 bg-surface px-2.5 py-1.5 text-sm outline-none';
			input.value = this.getAttribute('value') ?? '';
			// "input" is dispatched on every keystroke/segment change so the
			// "new row" draft editor -- which has no separate edit-mode step,
			// every editable column is always live-bound to draftValues -- can
			// keep that binding in sync. The single-cell edit path below
			// (commit on change/blur) doesn't listen for it, only draftValues
			// needs continuous updates; see text-edit's doc comment for why
			// both consumers share this one element regardless.
			input.addEventListener('input', () => {
				this.dispatchEvent(new CustomEvent('input', { detail: { value: input.value }, bubbles: true }));
			});
			// Both listeners can fire for one real edit (the browser's picker
			// fires "change" while still focused; closing it can then blur) --
			// _commit's own guard makes that harmless instead of a double write.
			input.addEventListener('change', () => this._commit(input.value));
			input.addEventListener('blur', () => this._commit(input.value));
			input.addEventListener('keydown', (e) => {
				if (e.key === 'Escape') {
					e.preventDefault();
					this._cancel();
				}
			});
			this._input = input;
			this.appendChild(input);
			// See text-edit's doc comment: only the single-cell edit path
			// (data-autofocus="true") should steal focus on mount -- the draft row
			// mounts every editable column's widget at once.
			if (this.getAttribute('data-autofocus') === 'true') {
				requestAnimationFrame(() => input.focus());
			}
		}
		_commit(value) {
			if (this._committed) return;
			this._committed = true;
			this.dispatchEvent(new CustomEvent('commit', { detail: { value }, bubbles: true }));
		}
		_cancel() {
			if (this._committed) return;
			this._committed = true;
			this.dispatchEvent(new CustomEvent('cancel', { bubbles: true }));
		}
	}
);
