import { Portal } from "solid-js/web";
import { createContext, useContext, createSignal, createEffect, onCleanup, createMemo, Show, For, JSXElement } from "solid-js";
import { Icon } from "./Icons.tsx";

export type ModalSize = "small" | "default" | "medium" | "large" | "xlarge" | "2xlarge" | "3xlarge" | "4xlarge" | "5xlarge" | "full";

export const MODAL_SMALL = "small";
export const MODAL_DEFAULT = "default";
export const MODAL_MEDIUM = "medium";
export const MODAL_LARGE = "large";
export const MODAL_XLARGE = "xlarge";
export const MODAL_2XLARGE = "2xlarge";
export const MODAL_3XLARGE = "3xlarge";
export const MODAL_4XLARGE = "4xlarge";
export const MODAL_5XLARGE = "5xlarge";
export const MODAL_FULL = "full";

const ANIMATION_DURATION = 100;

type Reactive<T> = T | (() => T);

interface ModalOptions {
    size?: ModalSize;
    centerOnScreen?: boolean;
}

interface ModalContextValue {
    openModal: (content: JSXElement, options?: ModalOptions) => void;
    closeModal: () => void;
    isOpen: () => boolean;
}

const ModalContext = createContext<ModalContextValue | null>(null);

const resolve = <T,>(val: Reactive<T>): T => typeof val === "function" ? (val as () => T)() : val;

export function useModal(): ModalContextValue {
    const context = useContext(ModalContext);
    if (!context) {
        throw new Error("useModal must be used within a ModalProvider");
    }
    return context;
}

// Shared stack of currently-open modals. Each modal pushes a token while open;
// Escape only dismisses the top-most one, so nested modals (a modal opened from
// inside another) close one layer per press instead of all at once.
const openModalStack: object[] = [];

// While `isOpen()` is true, registers this modal on the shared stack and wires
// up an Escape handler that fires `onEscape` only when this modal is on top.
// Must be called inside a component/reactive owner (uses createEffect/onCleanup).
function useModalEscape(isOpen: () => boolean, onEscape: () => void) {
    createEffect(() => {
        if (!isOpen()) return;

        const token = {};
        openModalStack.push(token);

        const handleKeyDown = (e: KeyboardEvent) => {
            if (e.key !== "Escape") return;
            if (openModalStack[openModalStack.length - 1] !== token) return;
            e.preventDefault();
            onEscape();
        };
        document.addEventListener("keydown", handleKeyDown);

        onCleanup(() => {
            document.removeEventListener("keydown", handleKeyDown);
            const idx = openModalStack.indexOf(token);
            if (idx !== -1) openModalStack.splice(idx, 1);
        });
    });
}

// -- Tailwind class constants --------------------------------------
const CONTAINER_BASE = "fixed inset-0 z-[100] w-full h-dvh m-0 border-0 bg-transparent flex justify-center max-w-screen max-h-dvh";
const CONTAINER_TOP = "items-start pt-10";
const CONTAINER_CENTER = "items-center";

const BACKDROP = "fixed inset-0 bg-black/30";

const MODAL_BASE = "relative bg-surface shadow-md text-sm w-full rounded-default max-h-[calc(100dvh_-_5rem)] flex flex-col overflow-hidden";
const MODAL_SIZES: Record<ModalSize, string> = {
    small:    "max-w-md",
    default:  "max-w-xl",
    medium:   "max-w-2xl",
    large:    "max-w-3xl",
    xlarge:   "max-w-4xl",
    "2xlarge":"max-w-5xl",
    "3xlarge":"max-w-6xl",
    "4xlarge":"max-w-7xl",
    "5xlarge":"max-w-[90rem]",
    full:     "max-w-none",
};

const HEADER = "flex items-center justify-between py-5 px-7 pb-4 border-b border-line text-lg font-semibold text-ink";
const HEADER_CLOSE_ONLY = "flex items-center justify-end p-4 pb-1";
const CLOSE_BTN = "cursor-pointer text-ink-muted bg-transparent border-0 p-0 leading-none hover:text-ink";
const BODY = "px-7 py-6 overflow-y-auto flex-1 min-h-0 [background:linear-gradient(var(--color-surface),var(--color-surface))_bottom_/_100%_3rem_no-repeat_local,linear-gradient(to_bottom,transparent,var(--color-surface))_bottom_/_100%_3rem_no-repeat_scroll,var(--color-surface)]";
const FOOTER = "py-4 px-7 pb-[calc(1rem_+_env(safe-area-inset-bottom,0px))] flex flex-row justify-end gap-2 border-t border-line bg-surface-muted rounded-b-default";
const FOOTER_SPACER = "h-2";

const WIZARD_ERROR = "flex items-center gap-2 py-3 px-8 text-sm text-red-700 dark:text-red-400 bg-red-50 dark:bg-red-950/40 border-t border-red-200 dark:border-red-900";
const WIZARD_ERROR_ICON = "shrink-0 text-red-500";

// Confirm modal
const CONFIRM_WRAP = "flex justify-end gap-2";
const CONFIRM_CANCEL = "py-2 px-4 text-sm border border-line-strong rounded-default bg-transparent cursor-pointer hover:bg-surface-muted";
const CONFIRM_OK_BASE = "py-2 px-4 text-sm rounded-default border-0 cursor-pointer text-white";
const CONFIRM_OK_VARIANTS = {
    danger:  "bg-red-600 hover:bg-red-700",
    primary: "bg-primary hover:bg-primary-hover",
};

// Wizard header
const WIZARD_HEADER = "flex flex-col items-center gap-2 flex-1";
const WIZARD_TITLE_ROW = "flex items-center justify-between w-full";
const WIZARD_TITLE = "text-xl";
const WIZARD_STEP_NAME = "text-xs font-semibold text-ink-soft uppercase tracking-wider";
const WIZARD_STEPS = "flex items-center justify-between relative w-full max-w-64";
const WIZARD_TRACK = "absolute top-1/2 left-0 right-0 h-0.5 bg-surface-strong -translate-y-1/2";
const WIZARD_TRACK_FILL = "h-full bg-primary transition-[width] duration-300 ease-in-out";
const WIZARD_STEP_WRAP = "relative z-[1]";
const STEP_INDICATOR_BASE = "w-7 h-7 rounded-full flex items-center justify-center text-xs font-semibold border-2 shrink-0 transition-all duration-300 ease-in-out";
const STEP_INDICATOR_PENDING = "border-line-strong text-ink-faint bg-surface";
const STEP_INDICATOR_ACTIVE = "bg-primary text-white border-primary";
const STEP_INDICATOR_COMPLETED = "bg-primary text-white border-primary";

// Wizard footer
const WIZARD_FOOTER = "flex items-center justify-between w-full gap-2";
const WIZARD_BTN_BASE = "py-2 px-5 text-sm rounded-default cursor-pointer border-0 disabled:opacity-40 disabled:cursor-not-allowed";
const WIZARD_BTN_BACK = "bg-transparent border border-line-strong text-ink enabled:hover:bg-surface-muted";
const WIZARD_BTN_NEXT = "bg-fill-neutral text-on-fill-neutral enabled:hover:bg-fill-neutral-hover";
const WIZARD_BTN_FINISH = "bg-primary text-white enabled:hover:bg-red-700";

interface ModalDisplayProps {
    size?: Reactive<ModalSize | undefined>;
    centerOnScreen?: Reactive<boolean | undefined>;
    onClose?: () => void;
    children?: JSXElement;
}

function ModalDisplay(props: ModalDisplayProps) {
    const [isVisible, setIsVisible] = createSignal(false);

    createEffect(() => {
        requestAnimationFrame(() => {
            requestAnimationFrame(() => {
                setIsVisible(true);
            });
        });
    });

    const getBackdropStyle = () => ({
        opacity: isVisible() ? 1 : 0,
        transition: `opacity ${ANIMATION_DURATION}ms ease-out`,
    });

    const getModalStyle = () => ({
        opacity: isVisible() ? 1 : 0,
        transform: isVisible() ? "scale(1)" : "scale(0.95)",
        transition: `opacity ${ANIMATION_DURATION}ms cubic-bezier(0.4, 0, 0.2, 1), transform ${ANIMATION_DURATION}ms cubic-bezier(0.4, 0, 0.2, 1)`,
    });

    const getContainerClass = () => {
        const centered = resolve(props.centerOnScreen);
        return CONTAINER_BASE + " " + (centered ? CONTAINER_CENTER : CONTAINER_TOP);
    };

    const getModalClass = () => {
        const size = resolve(props.size) || MODAL_DEFAULT;
        return MODAL_BASE + " " + MODAL_SIZES[size];
    };

    const handleClose = () => {
        const fn = props.onClose;
        if (typeof fn === "function") {
            fn();
        }
    };

    return (
        <dialog open class={getContainerClass()}>
            <div class={BACKDROP} style={getBackdropStyle()} onclick={handleClose}></div>
            <div class={getModalClass()} style={getModalStyle()}>
                {resolve(props.children)}
            </div>
        </dialog>
    );
}

interface ModalProviderProps {
    children?: JSXElement;
}

export function ModalProvider(props: ModalProviderProps) {
    const [isOpen, setIsOpen] = createSignal(false);
    const [content, setContent] = createSignal<JSXElement>(null);
    const [options, setOptions] = createSignal<ModalOptions>({});

    const openModal = (modalContent: JSXElement, modalOptions: ModalOptions = {}) => {
        setContent(() => modalContent);
        setOptions(modalOptions);
        setIsOpen(true);
    };

    const closeModal = () => {
        setIsOpen(false);
        setContent(null);
        setOptions({});
    };

    useModalEscape(isOpen, closeModal);

    const value: ModalContextValue = {
        openModal,
        closeModal,
        isOpen,
    };

    return (
        <ModalContext.Provider value={value}>
            {props.children}
            <Portal>
                <Show when={isOpen()}>
                    <ModalDisplay size={options().size} centerOnScreen={options().centerOnScreen} onClose={closeModal} children={content()}/>
                </Show>
            </Portal>
        </ModalContext.Provider>
    );
}

interface ModalContentProps {
    header?: JSXElement;
    footer?: JSXElement;
    onClose?: () => void;
    children?: JSXElement;
}

export function ModalContent(props: ModalContentProps) {
    const { closeModal } = useModal();
    const handleClose = () => {
        const fn = props.onClose;
        if (typeof fn === "function") {
            fn();
        } else {
            closeModal();
        }
    };

    const header = () => props.header;
    const footer = () => props.footer;

    return [
        <Show when={header() === undefined}>
            <div class={HEADER_CLOSE_ONLY}>
                <button onclick={handleClose} class={CLOSE_BTN}>
                    <Icon icon="xmark" size={24}/>
                </button>
            </div>
        </Show>,
        <Show when={header() !== undefined && header() !== null}>
            <div class={HEADER}>
                {header()}
                <button onclick={handleClose} class={CLOSE_BTN}>
                    <Icon icon="xmark" size={24}/>
                </button>
            </div>
        </Show>,
        <div class={BODY}>{props.children}</div>,
        <Show when={footer() === undefined}>
            <div class={FOOTER_SPACER}></div>
        </Show>,
        <Show when={footer() !== undefined && footer() !== null}>
            <div class={FOOTER}>{footer()}</div>
        </Show>,
    ];
}

interface ModalProps {
    isOpen: Reactive<boolean>;
    onClose: () => void;
    size?: ModalSize;
    centerOnScreen?: boolean;
    header?: JSXElement;
    footer?: JSXElement;
    children?: JSXElement;
}

export function Modal(props: ModalProps) {
    const isOpen = () => {
        const val = props.isOpen;
        return typeof val === "function" ? (val as () => boolean)() : val;
    };

    const handleClose = () => {
        const fn = props.onClose;
        if (typeof fn === "function") {
            fn();
        }
    };

    const header = () => props.header;
    const footer = () => props.footer;
    const size = () => props.size || MODAL_DEFAULT;
    const centerOnScreen = () => props.centerOnScreen;

    useModalEscape(isOpen, handleClose);

    // Render through a Portal (to document.body) so a Modal nested inside
    // another Modal's body isn't clipped by the parent panel's overflow or
    // trapped by its `transform` (which would make it the containing block for
    // our `position: fixed` container). WizardModal/ModalProvider do the same.
    return (
        <Portal>
            <Show when={isOpen()}>
                <ModalDisplay size={size()} centerOnScreen={centerOnScreen()} onClose={handleClose}>
                    <Show when={header() === undefined}>
                        <div class={HEADER_CLOSE_ONLY}>
                            <button onclick={handleClose} class={CLOSE_BTN}>
                                <Icon icon="xmark" size={24}/>
                            </button>
                        </div>
                    </Show>
                    <Show when={header() !== undefined && header() !== null}>
                        <div class={HEADER}>
                            {header()}
                            <button onclick={handleClose} class={CLOSE_BTN}>
                                <Icon icon="xmark" size={24}/>
                            </button>
                        </div>
                    </Show>

                    <div class={BODY}>{props.children}</div>

                    <Show when={footer() === undefined}>
                        <div class={FOOTER_SPACER}></div>
                    </Show>
                    <Show when={footer() !== undefined && footer() !== null}>
                        <div class={FOOTER}>{footer()}</div>
                    </Show>
                </ModalDisplay>
            </Show>
        </Portal>
    );
}

interface ConfirmModalProps {
    isOpen: Reactive<boolean>;
    onClose: () => void;
    onConfirm: () => void;
    title?: string;
    message: string;
    confirmText?: string;
    cancelText?: string;
    confirmStyle?: "danger" | "primary";
}

export function ConfirmModal(props: ConfirmModalProps) {
    const isOpen = () => {
        const val = props.isOpen;
        return typeof val === "function" ? (val as () => boolean)() : val;
    };

    const handleClose = () => {
        const fn = props.onClose;
        if (typeof fn === "function") {
            fn();
        }
    };

    const title = () => props.title ?? "Confirm";
    const message = () => props.message;
    const confirmText = () => props.confirmText ?? "Confirm";
    const cancelText = () => props.cancelText ?? "Cancel";
    const confirmStyle = (): "danger" | "primary" => props.confirmStyle ?? "danger";

    const handleConfirm = () => {
        const fn = props.onConfirm;
        if (typeof fn === "function") {
            fn();
        }
        handleClose();
    };

    return (
        <Modal isOpen={isOpen()} onClose={handleClose} size={MODAL_SMALL} centerOnScreen={true} header={title()} footer={
                <div class={CONFIRM_WRAP}>
                    <button onclick={handleClose} class={CONFIRM_CANCEL}>
                        {cancelText()}
                    </button>
                    <button onclick={handleConfirm} class={CONFIRM_OK_BASE + " " + CONFIRM_OK_VARIANTS[confirmStyle()]}>
                        {confirmText()}
                    </button>
                </div>
            }>
            {message()}
        </Modal>
    );
}

export interface WizardStepContext {
    setCanContinue: (complete: boolean) => void;
    nextStep: () => void;
    prevStep: () => void;
}

export interface WizardStep {
    title: string;
    content: (stepContext: WizardStepContext) => JSXElement;
}

interface WizardModalProps {
    isOpen: Reactive<boolean>;
    onClose: () => void;
    onComplete: () => void;
    steps: WizardStep[];
    size?: ModalSize;
    centerOnScreen?: boolean;
    title?: string;
    finishText?: string;
    error?: Reactive<string | null | undefined>;
}

export function WizardModal(props: WizardModalProps) {
    const isOpen = () => {
        const val = props.isOpen;
        return typeof val === "function" ? (val as () => boolean)() : val;
    };

    const handleClose = () => {
        const fn = props.onClose;
        if (typeof fn === "function") {
            fn();
        }
    };

    const steps = () => props.steps || [];
    const size = () => props.size || MODAL_LARGE;
    const centerOnScreen = () => props.centerOnScreen;
    const finishText = () => props.finishText ?? "Finish";

    const [currentStep, setCurrentStep] = createSignal(0);
    const [stepContinueFlags, setStepContinueFlags] = createSignal<boolean[]>([]);
    const [openVersion, setOpenVersion] = createSignal(0);

    const totalSteps = () => steps().length;
    const isFirstStep = () => currentStep() === 0;
    const isLastStep = () => currentStep() === totalSteps() - 1;
    const title = () => props.title ?? steps()[currentStep()]?.title ?? "";
    const canContinue = () => !!stepContinueFlags()[currentStep()];

    const makeSetCanContinue = (stepIndex: number) => (value: boolean) => {
        setStepContinueFlags((prev) => {
            const next = [...prev];
            next[stepIndex] = value;
            return next;
        });
    };

    createEffect(() => {
        if (isOpen()) {
            setCurrentStep(0);
            setStepContinueFlags([]);
            setOpenVersion(v => v + 1);
        }
    });

    const nextStep = () => {
        if (!isLastStep()) {
            setCurrentStep((s) => s + 1);
        }
    };

    const prevStep = () => {
        if (!isFirstStep()) {
            setCurrentStep((s) => s - 1);
        }
    };

    const handleNext = () => {
        if (isLastStep()) {
            const fn = props.onComplete;
            if (typeof fn === "function") {
                fn();
            }
        } else {
            nextStep();
        }
    };

    useModalEscape(isOpen, handleClose);

    const renderedSteps = createMemo(() => {
        openVersion();
        return steps().map((step, index) => {
            const stepContext: WizardStepContext = {
                setCanContinue: makeSetCanContinue(index),
                nextStep,
                prevStep,
            };
            return step.content(stepContext);
        });
    });

    const currentStepTitle = () => steps()[currentStep()]?.title ?? "";
    const progressPercent = () => totalSteps() <= 1 ? 100 : (currentStep() / (totalSteps() - 1)) * 100;

    const stepIndicatorClass = (i: number, cur: number): string => {
        let c = STEP_INDICATOR_BASE + " ";
        if (i === cur) c += STEP_INDICATOR_ACTIVE;
        else if (i < cur) c += STEP_INDICATOR_COMPLETED;
        else c += STEP_INDICATOR_PENDING;
        return c;
    };

    const header = () => (
        <div class={WIZARD_HEADER}>
            <div class={WIZARD_TITLE_ROW}>
                <span class={WIZARD_TITLE}>{title()}</span>
                <button onclick={handleClose} class={CLOSE_BTN}>
                    <Icon icon="xmark" size={24}/>
                </button>
            </div>
            <div class={WIZARD_STEP_NAME}>{currentStepTitle()}</div>
            <div class={WIZARD_STEPS}>
                <div class={WIZARD_TRACK}>
                    <div class={WIZARD_TRACK_FILL} style={`width:${progressPercent()}%`}/>
                </div>
                <For each={steps()}>{(_step, index) => (
                    <div class={WIZARD_STEP_WRAP}>
                        <div class={stepIndicatorClass(index(), currentStep())}>{index() < currentStep() ? "✓" : index() + 1}</div>
                    </div>
                )}</For>
            </div>
        </div>
    );

    const nextBtnClass = () => WIZARD_BTN_BASE + " " + (isLastStep() ? WIZARD_BTN_FINISH : WIZARD_BTN_NEXT);

    const footer = () => (
        <div class={WIZARD_FOOTER}>
            <button onclick={prevStep} class={WIZARD_BTN_BASE + " " + WIZARD_BTN_BACK} disabled={isFirstStep()}>Back</button>
            <button onclick={handleNext} class={nextBtnClass()} disabled={!canContinue()}>{isLastStep() ? finishText() : "Next"}</button>
        </div>
    );

    return (
        <Portal>
            <Show when={isOpen()}>
                <ModalDisplay size={size()} centerOnScreen={centerOnScreen()} onClose={handleClose}>
                    <div class={HEADER}>
                        {header()}
                    </div>

                    <div class={BODY}>
                        <For each={renderedSteps()}>{(content, index) => (
                            <div style={index() === currentStep()
                                    ? ""
                                    : "display:none"
                                }>{content}</div>
                        )}</For>
                    </div>

                    <Show when={(() => { const e = props.error; return typeof e === "function" ? (e as () => JSXElement)() : e; })()}>
                        <div class={WIZARD_ERROR}>
                            <Icon icon="circle-exclamation" size={16} class={WIZARD_ERROR_ICON}/>
                            <span>{(() => { const e = props.error; return typeof e === "function" ? (e as () => JSXElement)() : e; })()}</span>
                        </div>
                    </Show>

                    <div class={FOOTER}>
                        {footer()}
                    </div>
                </ModalDisplay>
            </Show>
        </Portal>
    );
}
