import { createSignal, createEffect, Show, onMount, onCleanup, For, JSX } from "solid-js";
import { Portal } from "solid-js/web";
import { Icon } from "./Icons.tsx";
import { FormInput } from "./Forms.tsx";
import { readAccessor, accessor, type MaybeAccessor } from "../utils/accessors.ts";
import {
    CAL_PICKER_ROOT,
    CAL_HEADER_PICKER,
    CAL_NAV_BTN,
    CAL_MY_PICKER,
    CAL_WEEKDAYS_PICKER,
    CAL_WEEKDAY_PICKER,
    CAL_DAYS_PICKER,
    CAL_DAY_PICKER_BASE,
    CAL_SELECT,
} from "./Calendar.tsx";

const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

function getDaysInMonth(year: number, month: number): number {
    return new Date(year, month + 1, 0).getDate();
}

function getFirstDayOfMonth(year: number, month: number): number {
    return new Date(year, month, 1).getDay();
}

function dayClass(date: Date | null, selected: boolean, today: boolean): string {
    let c = CAL_DAY_PICKER_BASE;
    if (!date) c += " cursor-default";
    else c += " hover:bg-surface-raised";
    if (today) c += " font-bold text-primary";
    if (selected) c += " !bg-primary !text-white";
    return c;
}

/** Parse typed or pasted text into YYYY-MM-DD, or "" if invalid. */
function parseDateInput(text: string): string {
    const trimmed = text.trim();
    if (!trimmed) return "";

    if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
        const [y, m, d] = trimmed.split("-").map((n) => parseInt(n, 10));
        const iso = new Date(y, m - 1, d);
        if (!isNaN(iso.getTime()) && iso.getFullYear() === y && iso.getMonth() === m - 1 && iso.getDate() === d) {
            return trimmed;
        }
    }

    const parsed = new Date(trimmed);
    if (!isNaN(parsed.getTime())) {
        const y = parsed.getFullYear();
        const m = String(parsed.getMonth() + 1).padStart(2, "0");
        const d = String(parsed.getDate()).padStart(2, "0");
        return `${y}-${m}-${d}`;
    }

    return "";
}

function formatDisplayDate(iso: string): string {
    if (!iso) return "";
    const parts = iso.split("-");
    if (parts.length < 3) return iso;
    const d = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
    if (isNaN(d.getTime())) return "";
    return d.toLocaleDateString();
}

interface CalendarDropdownProps {
    selected?: MaybeAccessor<string>;
    onSelect?: (key: string) => void;
}

function CalendarDropdown(props: CalendarDropdownProps) {
    const today = new Date();
    const selected = () => readAccessor(props.selected, "");
    const [viewDate, setViewDate] = createSignal(selected() ? new Date(selected()) : today);
    const [key, setKey] = createSignal(0);

    createEffect(() => {
        const v = selected();
        if (v) {
            const d = new Date(v);
            if (!isNaN(d.getTime())) {
                setViewDate(d);
            }
        }
    });

    const currentMonth = () => viewDate().getMonth();
    const currentYear = () => viewDate().getFullYear();

    const getDays = () => {
        const year = currentYear();
        const month = currentMonth();
        const daysInMonth = getDaysInMonth(year, month);
        const firstDay = getFirstDayOfMonth(year, month);

        const daysArray: (Date | null)[] = [];
        for (let i = 0; i < firstDay; i++) {
            daysArray.push(null);
        }
        for (let i = 1; i <= daysInMonth; i++) {
            daysArray.push(new Date(year, month, i));
        }
        return daysArray;
    };

    const isSelected = (date: Date | null): boolean => {
        if (!date) return false;
        const v = selected();
        if (!v) return false;
        const sel = new Date(v);
        if (isNaN(sel.getTime())) return false;
        return date.getFullYear() === sel.getFullYear() &&
               date.getMonth() === sel.getMonth() &&
               date.getDate() === sel.getDate();
    };

    const isToday = (date: Date | null): boolean => {
        if (!date) return false;
        return date.getFullYear() === today.getFullYear() &&
               date.getMonth() === today.getMonth() &&
               date.getDate() === today.getDate();
    };

    const selectDate = (date: Date | null) => {
        if (!date) return;
        const y = date.getFullYear();
        const m = String(date.getMonth() + 1).padStart(2, '0');
        const d = String(date.getDate()).padStart(2, '0');
        props.onSelect?.(`${y}-${m}-${d}`);
    };

    const goPrev = (e: MouseEvent) => {
        e.stopPropagation();
        setViewDate(new Date(currentYear(), currentMonth() - 1, 1));
        setKey(k => k + 1);
    };

    const goNext = (e: MouseEvent) => {
        e.stopPropagation();
        setViewDate(new Date(currentYear(), currentMonth() + 1, 1));
        setKey(k => k + 1);
    };

    return (
        <div class={CAL_PICKER_ROOT} attr:key={key()}>
            <div class={CAL_HEADER_PICKER}>
                <button type="button" class={CAL_NAV_BTN} onclick={goPrev}>
                    <Icon icon="chevron-left" size={16}/>
                </button>
                <span class={CAL_MY_PICKER}>{MONTHS[currentMonth()]}&nbsp;{currentYear()}</span>
                <button type="button" class={CAL_NAV_BTN} onclick={goNext}>
                    <Icon icon="chevron-right" size={16}/>
                </button>
            </div>
            <div class={CAL_WEEKDAYS_PICKER}>
                <For each={DAYS}>{(day) => <div class={CAL_WEEKDAY_PICKER}>{day}</div>}</For>
            </div>
            <div class={CAL_DAYS_PICKER}>
                <For each={getDays()}>{(date) => (
                    <button type="button" class={dayClass(date, isSelected(date), isToday(date))} onclick={(e: MouseEvent) => { e.stopPropagation(); selectDate(date); }} disabled={!date}>
                        {date ? date.getDate() : ""}
                    </button>
                )}</For>
            </div>
        </div>
    );
}

const DATE_PICKER_WRAP = "relative w-full min-w-0";
const DATE_PICKER_FIELD = "relative w-full min-w-0 cursor-pointer [&_.ui-form]:m-0 [&_input]:cursor-text";
const DATE_PICKER_DROPDOWN = "bg-surface border border-line rounded-default shadow-[0_4px_12px_rgba(0,0,0,0.15)] p-1 min-w-[16rem]";
const DATE_PICKER_ICON_BTN = "absolute inset-y-0 right-0 z-[1] flex items-center justify-center bg-transparent border-0 px-2 cursor-pointer text-ink-muted leading-none hover:text-ink pointer-events-auto";

const DATE_PICKER_CLEAR_BTN = "absolute inset-y-0 right-8 z-[1] flex items-center justify-center bg-transparent border-0 px-1.5 cursor-pointer text-ink-muted leading-none hover:text-ink pointer-events-auto";

interface DatePickerProps {
    id?: string;
    name?: string;
    value?: string;
    oninput?: JSX.InputEventHandlerUnion<HTMLInputElement, InputEvent>;
    onchange?: (value: string) => void;
    placeholder?: string;
    small?: boolean;
    clearable?: boolean;
}

export function DatePicker(props: DatePickerProps) {
    const [open, setOpen] = createSignal(false);
    const [localValue, setLocalValue] = createSignal("");
    const [editing, setEditing] = createSignal(false);
    const [draft, setDraft] = createSignal("");
    const [dropdownPos, setDropdownPos] = createSignal({ top: 0, left: 0, width: 0 });
    let containerRef: HTMLDivElement | undefined;
    let fieldRef: HTMLDivElement | undefined;
    let dropdownRef: HTMLDivElement | undefined;

    const externalValue = () => readAccessor(props.value, "");

    const updateDropdownPos = () => {
        if (!fieldRef) return;
        const rect = fieldRef.getBoundingClientRect();
        setDropdownPos({ top: rect.bottom + 4, left: rect.left, width: rect.width });
    };

    const dropdownStyle = () => {
        const pos = dropdownPos();
        const maxW = Math.min(400, window.innerWidth - pos.left - 8);
        return `position:fixed;top:${pos.top}px;left:${pos.left}px;min-width:${Math.max(pos.width, 16 * 16)}px;width:max-content;max-width:${maxW}px;z-index:200;`;
    };

    createEffect(() => {
        if (open()) {
            updateDropdownPos();
            let rafId: number;
            const trackPosition = () => {
                updateDropdownPos();
                rafId = requestAnimationFrame(trackPosition);
            };
            rafId = requestAnimationFrame(trackPosition);
            onCleanup(() => cancelAnimationFrame(rafId));
        }
    });

    createEffect(() => {
        if (!editing()) {
            setLocalValue(externalValue());
        }
    });

    const hasValue = () => !!localValue();

    const displayValue = () => formatDisplayDate(localValue());

    const inputValue = () => editing() ? draft() : displayValue();

    const commitValue = (raw: string) => {
        const parsed = parseDateInput(raw);
        setLocalValue(parsed);
        setDraft(parsed ? formatDisplayDate(parsed) : "");
        if (typeof props.onchange === "function") props.onchange(parsed);
    };

    const handleSelect = (dateStr: string) => {
        setEditing(false);
        setLocalValue(dateStr);
        setDraft(formatDisplayDate(dateStr));
        if (typeof props.onchange === "function") props.onchange(dateStr);
        setOpen(false);
    };

    const handleClear = (e: MouseEvent) => {
        e.stopPropagation();
        setEditing(false);
        setLocalValue("");
        setDraft("");
        if (typeof props.onchange === "function") props.onchange("");
        setOpen(false);
    };

    const handleClickOutside = (e: MouseEvent) => {
        const target = e.target as Node;
        const inContainer = containerRef?.contains(target);
        const inDropdown = dropdownRef?.contains(target);
        if (!inContainer && !inDropdown) {
            if (editing()) {
                commitValue(draft());
                setEditing(false);
            }
            setOpen(false);
        }
    };

    onMount(() => {
        document.addEventListener("click", handleClickOutside);
    });

    onCleanup(() => {
        document.removeEventListener("click", handleClickOutside);
    });

    const openCalendar = (_e: MouseEvent) => {
        updateDropdownPos();
        setOpen(true);
    };

    const toggleCalendar = (e: MouseEvent) => {
        e.stopPropagation();
        if (!open()) updateDropdownPos();
        setOpen((v) => !v);
    };

    const handleInputFocus = (_e: FocusEvent) => {
        setEditing(true);
        setDraft(displayValue());
    };

    const handleInput = (e: InputEvent & { currentTarget: HTMLInputElement }) => {
        setDraft(e.currentTarget.value);
    };

    const handleInputBlur = (_e: FocusEvent) => {
        commitValue(draft());
        setEditing(false);
    };

    const inputCls = () => "w-full" + (props.clearable && hasValue() ? " pr-14" : " pr-9");

    return (
        <div class={DATE_PICKER_WRAP} ref={(el: HTMLDivElement) => containerRef = el}>
            <div class={DATE_PICKER_FIELD} ref={(el: HTMLDivElement) => fieldRef = el} onclick={openCalendar}>
                <FormInput
                    type="text"
                    small={props.small}
                    value={inputValue()}
                    placeholder={props.placeholder || "Select date"}
                    onfocus={handleInputFocus}
                    oninput={handleInput}
                    onblur={handleInputBlur}
                    class={inputCls()}
                />
                <Show when={props.clearable && hasValue()}>
                    <button type="button" class={DATE_PICKER_CLEAR_BTN} onclick={handleClear} aria-label="Clear date">
                        <Icon icon="xmark" size={14} class="block leading-none"/>
                    </button>
                </Show>
                <button type="button" class={DATE_PICKER_ICON_BTN} onclick={toggleCalendar} aria-label="Open calendar">
                    <Icon icon="calendar" size={16} class="block leading-none"/>
                </button>
            </div>
            <Show when={open()}>
                <Portal>
                    <div
                        ref={(el: HTMLDivElement) => dropdownRef = el}
                        data-floating-content="true"
                        class={DATE_PICKER_DROPDOWN}
                        style={dropdownStyle()}
                        onclick={(e: MouseEvent) => e.stopPropagation()}
                    >
                        <CalendarDropdown selected={localValue} onSelect={handleSelect}/>
                    </div>
                </Portal>
            </Show>
        </div>
    );
}

const MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

function CalendarDropdownDOB(props: CalendarDropdownProps) {
    const today = new Date();
    const currentYear = today.getFullYear();
    const years = Array.from({ length: 120 }, (_, i) => currentYear - i);

    const [viewDate, setViewDate] = createSignal(props.selected ? new Date(props.selected as string) : today);
    const [key, setKey] = createSignal(0);

    createEffect(() => {
        if (props.selected) {
            const d = new Date(props.selected as string);
            if (!isNaN(d.getTime())) {
                setViewDate(d);
            }
        }
    });

    const currentMonth = () => viewDate().getMonth();
    const currentYearView = () => viewDate().getFullYear();

    const getDays = () => {
        const year = currentYearView();
        const month = currentMonth();
        const daysInMonth = getDaysInMonth(year, month);
        const firstDay = getFirstDayOfMonth(year, month);

        const daysArray: (Date | null)[] = [];
        for (let i = 0; i < firstDay; i++) {
            daysArray.push(null);
        }
        for (let i = 1; i <= daysInMonth; i++) {
            daysArray.push(new Date(year, month, i));
        }
        return daysArray;
    };

    const handleMonthChange = (e: Event) => {
        const month = parseInt((e.target as HTMLSelectElement).value);
        if (!isNaN(month)) {
            setViewDate(new Date(currentYearView(), month, 1));
        }
    };

    const handleYearChange = (e: Event) => {
        const year = parseInt((e.target as HTMLSelectElement).value);
        if (!isNaN(year)) {
            setViewDate(new Date(year, currentMonth(), 1));
        }
    };

    const isSelected = (date: Date | null): boolean => {
        if (!date || !props.selected) return false;
        const sel = new Date(props.selected as string);
        if (isNaN(sel.getTime())) return false;
        return date.getFullYear() === sel.getFullYear() &&
               date.getMonth() === sel.getMonth() &&
               date.getDate() === sel.getDate();
    };

    const isToday = (date: Date | null): boolean => {
        if (!date) return false;
        return date.getFullYear() === today.getFullYear() &&
               date.getMonth() === today.getMonth() &&
               date.getDate() === today.getDate();
    };

    const selectDate = (date: Date | null) => {
        if (!date) return;
        const y = date.getFullYear();
        const m = String(date.getMonth() + 1).padStart(2, '0');
        const d = String(date.getDate()).padStart(2, '0');
        props.onSelect?.(`${y}-${m}-${d}`);
    };

    const goPrev = (e: MouseEvent) => {
        e.stopPropagation();
        setViewDate(new Date(currentYearView(), currentMonth() - 1, 1));
        setKey(k => k + 1);
    };

    const goNext = (e: MouseEvent) => {
        e.stopPropagation();
        setViewDate(new Date(currentYearView(), currentMonth() + 1, 1));
        setKey(k => k + 1);
    };

    return (
        <div class={CAL_PICKER_ROOT} attr:key={key()}>
            <div class={CAL_HEADER_PICKER}>
                <button type="button" class={CAL_NAV_BTN} onclick={goPrev}>
                    <Icon icon="chevron-left" size={16}/>
                </button>
                <select class={CAL_SELECT} value={currentMonth()} onchange={handleMonthChange}>
                    <For each={MONTHS_SHORT}>{(m, i) => <option value={i()}>{m}</option>}</For>
                </select>
                <select class={CAL_SELECT} value={currentYearView()} onchange={handleYearChange}>
                    <For each={years}>{(y) => <option value={y}>{y}</option>}</For>
                </select>
                <button type="button" class={CAL_NAV_BTN} onclick={goNext}>
                    <Icon icon="chevron-right" size={16}/>
                </button>
            </div>
            <div class={CAL_WEEKDAYS_PICKER}>
                <For each={DAYS}>{(day) => <div class={CAL_WEEKDAY_PICKER}>{day}</div>}</For>
            </div>
            <div class={CAL_DAYS_PICKER}>
                <For each={getDays()}>{(date) => (
                    <button type="button" class={dayClass(date, isSelected(date), isToday(date))} onclick={(e: MouseEvent) => { e.stopPropagation(); selectDate(date); }} disabled={!date}>
                        {date ? date.getDate() : ""}
                    </button>
                )}</For>
            </div>
        </div>
    );
}

export function DateOfBirthPicker(props: DatePickerProps) {
    const [open, setOpen] = createSignal(false);
    const [editing, setEditing] = createSignal(false);
    const [draft, setDraft] = createSignal("");
    let containerRef: HTMLDivElement | undefined;

    const isoValue = () => readAccessor(props.value, "");

    const displayValue = () => formatDisplayDate(isoValue());

    const inputValue = () => editing() ? draft() : displayValue();

    const commitValue = (raw: string) => {
        const parsed = parseDateInput(raw);
        setDraft(parsed ? formatDisplayDate(parsed) : "");
        props.onchange?.(parsed);
    };

    const handleSelect = (dateStr: string) => {
        setEditing(false);
        setDraft(formatDisplayDate(dateStr));
        props.onchange?.(dateStr);
        setOpen(false);
    };

    const handleClickOutside = (e: MouseEvent) => {
        if (containerRef && !containerRef.contains(e.target as Node)) {
            if (editing()) {
                commitValue(draft());
                setEditing(false);
            }
            setOpen(false);
        }
    };

    onMount(() => {
        document.addEventListener("click", handleClickOutside);
    });

    onCleanup(() => {
        document.removeEventListener("click", handleClickOutside);
    });

    const openCalendar = (_e: MouseEvent) => {
        setOpen(true);
    };

    const toggleCalendar = (e: MouseEvent) => {
        e.stopPropagation();
        setOpen((v) => !v);
    };

    return (
        <div class={DATE_PICKER_WRAP} ref={(el: HTMLDivElement) => containerRef = el}>
            <div class={DATE_PICKER_FIELD} onclick={openCalendar}>
                <FormInput
                    type="text"
                    value={inputValue()}
                    placeholder={props.placeholder || "Select date of birth"}
                    onfocus={(_e: FocusEvent) => { setEditing(true); setDraft(displayValue()); }}
                    oninput={(e: InputEvent & { currentTarget: HTMLInputElement }) => setDraft(e.currentTarget.value)}
                    onblur={(_e: FocusEvent) => { commitValue(draft()); setEditing(false); }}
                    class="w-full pr-9"
                />
                <button type="button" class={DATE_PICKER_ICON_BTN} onclick={toggleCalendar} aria-label="Open calendar">
                    <Icon icon="calendar" size={16} class="block leading-none"/>
                </button>
            </div>
            <Show when={open()}>
                <div class={DATE_PICKER_DROPDOWN} onclick={(e: MouseEvent) => e.stopPropagation()}>
                    <CalendarDropdownDOB selected={isoValue} onSelect={handleSelect}/>
                </div>
            </Show>
        </div>
    );
}
