import { CSSProperties, FC, ReactNode, useEffect, useLayoutEffect, useRef, useState, ElementType } from 'react';
import guildColorBottom from '../../../assets/images/groups/native/group_guild_color_btm.png';
import dropmenuArrow from '../../../assets/images/habbo-skin/slices/dropmenu-default-arrow.png';
import { NativeText } from '../../../common/native-text/NativeText';
import { NativeFontStyle } from '../../../common/native-text/NativeFont';
import { NativeTextStyleName } from '../../../common/native-text/NativeTextStyles';
import { BaseButton } from '../../../common/BaseButton';
import { ListSelect } from '../../../common/ListSelect';

// groups_main_window client coordinates: the frame draws 33px of title above them.
export const GROUP_SURFACE = 0xe9e9e1;
export const GROUP_HEADER_SURFACE = 0xb3b099;
// The caption's field sits above the 33px frame header; its glyphs start 12px below the window top.
const TITLE_Y = 9;

interface GroupTextProps {
    text: string;
    x: number;
    y: number;
    width?: number;
    height?: number;
    textStyle?: NativeTextStyleName;
    overrides?: Partial<NativeFontStyle>;
    background?: number;
    align?: 'left' | 'center';
    leading?: number;
    /** word_wrap / multiline fields wrap at their width; auto_size fields never do. */
    wrap?: boolean;
    /** Draw the glyphs onto a skin instead of a flat surface: black text multiplies over white, white text screens over black. */
    blend?: 'multiply' | 'screen';
    className?: string;
    onClick?: () => void;
}

/** html text fields turn <br/> into line breaks; the rest of these strings carry no markup. */
const toNativeText = (text: string) => text.replace(/<br\s*\/?>/gi, '\n').replace(/\n+$/, '');

/** One v75 TextField placed at its layout rectangle (x, y, width, height). */
export const GroupText: FC<GroupTextProps> = ({
    text,
    x,
    y,
    width,
    height,
    textStyle = 'u_regular',
    overrides,
    background = GROUP_SURFACE,
    align = 'left',
    leading,
    wrap = false,
    blend,
    className = '',
    onClick
}) =>
{
    const textRef = useRef<HTMLDivElement>(null);
    const Tag: ElementType = onClick ? BaseButton : 'div';
    const [centerOffset, setCenterOffset] = useState(0);
    const isCentered = align === 'center' && width !== undefined;

    // auto_size center puts the field at floor((width - fieldWidth) / 2).
    useLayoutEffect(() =>
    {
        const field = textRef.current?.firstElementChild;

        if (!isCentered || !field) return;

        const center = () => setCenterOffset(Math.floor((width - field.getBoundingClientRect().width) / 2));
        const observer = new ResizeObserver(center);

        center();
        observer.observe(field);

        return () => observer.disconnect();
    }, [isCentered, width, text]);

    // A TextField keeps its 2px gutter at the bottom: rows below height - 2 are clipped.
    const style: CSSProperties = {
        mixBlendMode: blend,
        left: x + (isCentered ? centerOffset : 0),
        top: y,
        width: wrap ? width : undefined,
        height: height === undefined ? undefined : height - 2
    };

    return (
        <Tag ref={textRef} {...(onClick ? { type: 'button' } : {})} className={`octane-group-native__text${height === undefined ? '' : ' is-clipped'} ${className}`} style={style} onClick={onClick}>
            <NativeText
                background={blend === 'multiply' ? 0xffffff : blend === 'screen' ? 0x000000 : background}
                leading={leading}
                maxWidth={wrap ? width : undefined}
                overrides={overrides}
                text={toNativeText(text)}
                textStyle={textStyle}
            />
        </Tag>
    );
};

/** Window text with the explicit font variables of the group layouts (sharpness 0, thickness 0). */
export const flatText = (size: number, extra: Partial<NativeFontStyle> = {}): Partial<NativeFontStyle> => ({ size, sharpness: 0, thickness: 0, ...extra });

interface GroupBoxProps {
    kind: 'white' | 'outline' | 'yellow' | 'red' | 'gray' | 'cc' | 'slot' | 'tan' | 'dark';
    onClick?: () => void;
    x: number;
    y: number;
    width: number;
    height: number;
    className?: string;
    children?: ReactNode;
}

/** border style 0 (white, or tinted 0xffc300 / 0xcc0000 / 0xaaaaaa), 5 (outline), or style 3 (slot) white or tinted 0xe9e9e1 / 0xbebba5. */
export const GroupBox: FC<GroupBoxProps> = ({ kind, x, y, width, height, className = '', onClick, children }) => {
    const Tag: ElementType = onClick ? BaseButton : 'div';

    return (
    <Tag {...(onClick ? { type: 'button' } : {})} className={`octane-group-native__box is-${kind} ${className}`} style={{ left: x, top: y, width, height }} onClick={onClick}>
        {children}
    </Tag>
);
};

interface GroupButtonProps {
    label: string;
    x: number;
    y: number;
    width: number;
    height: number;
    disabled?: boolean;
    /** Pixels the native client sits the caption right of the centred position (the narrow Purchase button). */
    labelShift?: number;
    onClick: () => void;
}

/** button_thick, style 3 (habbo_skin_button_shiny_thick). */
export const GroupButton: FC<GroupButtonProps> = ({ label, x, y, width, height, disabled = false, labelShift = 0, onClick }) => (
    <BaseButton
        className="octane-group-native__button"
        disabled={disabled}
        style={{ left: x, top: y, width, height, paddingLeft: labelShift * 2 }}
        type="button"
        onClick={onClick}
    >
        <GroupText blend="multiply" className="is-static" text={label} textStyle="u_bold" x={0} y={0} />
    </BaseButton>
);

/** group_guild_color_btm tinted with the guild colour, group_guild_color_top drawn over it. */
export const GroupSwatch: FC<{ color: string; x: number; y: number }> = ({ color, x, y }) => (
    <div className="octane-group-native__swatch" style={{ left: x, top: y, '--group-color': '#' + color } as CSSProperties}>
        <img alt="" draggable={false} src={guildColorBottom} />
        <span className="octane-group-native__swatch-fill" />
    </div>
);

interface GroupInputProps {
    label: string;
    value: string;
    maxLength: number;
    x: number;
    y: number;
    width: number;
    height: number;
    multiline?: boolean;
    /** The forum compose fields have no border, a larger face and their own text margin. */
    plain?: boolean;
    fontSize?: number;
    inset?: number;
    onChange: (value: string) => void;
}

/**
 * An editable input TextField. The browser input keeps its own visible text while it has focus or whenever the v75 raster would not
 * sit inside the field (overflow, unsupported glyphs), so caret, selection, scrolling and IME stay the browser's; at rest the
 * v75 raster is drawn over the input.
 */
export const GroupInput: FC<GroupInputProps> = ({ label, value, maxLength, x, y, width, height, multiline = false, plain = false, fontSize = 13, inset = 1, onChange }) =>
{
    const overlayRef = useRef<HTMLDivElement>(null);
    const [isEditing, setIsEditing] = useState(false);
    const [rasterFits, setRasterFits] = useState(false);

    // The raster fits when it was drawn natively (no glyph fallback) and stays inside the bordered field.
    useLayoutEffect(() =>
    {
        const overlay = overlayRef.current;
        const field = overlay?.firstElementChild as HTMLElement | null;

        if (!field) return;

        const measure = () =>
        {
            const isNative = field.dataset.nativeText !== 'fallback';

            setRasterFits(isNative && field.offsetWidth <= width - 2 && field.offsetHeight <= height - 2);
        };
        const observer = new ResizeObserver(measure);
        const mutations = new MutationObserver(measure);

        measure();
        observer.observe(field);
        mutations.observe(field, { attributes: true, attributeFilter: ['data-native-text'] });

        return () =>
        {
            observer.disconnect();
            mutations.disconnect();
        };
    }, [value, width, height, multiline]);

    const showRaster = !isEditing && rasterFits && value.length > 0;
    const inputProps = {
        'aria-label': label,
        className: 'octane-group-native__input' + (showRaster ? ' is-raster' : ''),
        maxLength,
        style: plain ? { fontSize, lineHeight: Math.round(fontSize * 1.25) + 'px', paddingLeft: inset, paddingRight: inset } : undefined,
        value,
        onBlur: () => setIsEditing(false),
        onFocus: () => setIsEditing(true)
    };

    return (
        <div className={`octane-group-native__field${multiline ? ' is-multiline' : ''}${plain ? ' is-plain' : ''}${plain && value.length === 0 ? ' is-empty' : ''}`} style={{ left: x, top: y, width, height }}>
            {multiline ? (
                <textarea {...inputProps} onChange={(event) => onChange(event.target.value)} />
            ) : (
                <input {...inputProps} type="text" onChange={(event) => onChange(event.target.value)} />
            )}
            <div ref={overlayRef} aria-hidden="true" className={`octane-group-native__field-text${showRaster ? '' : ' is-hidden'}`} style={plain ? { top: 0, left: inset - 1 } : undefined}>
                <NativeText background={null} maxWidth={multiline ? width - (plain ? 2 * (inset - 1) : 0) : undefined} overrides={flatText(fontSize)} text={value} textStyle="u_regular" />
            </div>
        </div>
    );
};

/** The frame caption: u_frame_title drawn with the v75 raster, centred across the whole window. */
export const GroupWindowTitle: FC<{ title: string; width: number }> = ({ title, width }) => (
    <GroupText
        align="center"
        blend="screen"
        className="is-title"
        overrides={{ color: 0xffffff }}
        text={title}
        textStyle="u_frame_title"
        width={width}
        x={0}
        y={TITLE_Y}
    />
);

interface GroupDropMenuOption {
    value: number;
    label: string;
}

interface GroupDropMenuProps {
    label: string;
    options: GroupDropMenuOption[];
    value: number;
    x: number;
    y: number;
    width: number;
    height: number;
    onSelect: (value: number) => void;
}

/** A dropmenu (style 0): the chosen caption on the default skin; a click opens the option list under it. */
export const GroupDropMenu: FC<GroupDropMenuProps> = ({ label, options, value, x, y, width, height, onSelect }) =>
{
    const selected = options.find((option) => option.value === value) ?? options[0];

    return (
        <div className="octane-group-native__dropmenu-root" style={{ position: 'absolute', left: x, top: y, width, height }}>
            <ListSelect
                aria-label={label}
                itemClassName="octane-group-native__dropmenu-item"
                options={options.map((option) => ({ value: String(option.value), label: <GroupText blend="multiply" text={option.label} x={0} y={1} /> }))}
                popupClassName="octane-group-native__dropmenu-list"
                positionerClassName="octane-group-native__dropmenu-positioner"
                renderTrigger={() => (
                    <>
                        <GroupText blend="multiply" text={selected?.label ?? ''} width={width - 30} height={height - 4} x={8} y={3} />
                        <img alt="" draggable={false} src={dropmenuArrow} />
                    </>
                )}
                scaleWithTrigger
                sideOffset={-1}
                triggerClassName="octane-group-native__dropmenu"
                value={String(selected?.value ?? '')}
                onValueChange={(next) => onSelect(Number(next))}
            />
        </div>
    );
};
