import {
    Attributes,
    ComponentChild,
    ComponentChildren,
    JSX,
    VNode,
    toChildArray,
} from "preact";
import { render } from "preact-render-to-string";
import { writeText } from "copy-paste";
// i know it's out of date; i just want something simple
import { randomInt } from "https://deno.land/std@0.146.0/node/internal/crypto/random.ts";

export function Main({
    children,
    ...attributes
}: {
    children?: ComponentChildren;
    style?: JSX.CSSProperties;
    attributes?: JSX.HTMLAttributes;
}) {
    return (
        <div {...attributes} id="main">
            {...toChildArray(children)}
        </div>
    );
}

export const HCenter = ({
    children,
    ...attrs
}: {
    children: ComponentChildren;
    attrs?: Attributes;
}) => (
    <div
        {...attrs}
        class="hcenter"
        style="display: flex; justify-content: center;"
    >
        {...toChildArray(children)}
    </div>
);

export const eggbug_emotions = (attrs: JSX.HTMLAttributes<HTMLImageElement>) =>
    ({
        smiling: (
            <img
                class="eggbug"
                src="https://staging.cohostcdn.org/attachment/f33b4285-0455-4128-96b8-117054af40c3/eggbugSquare.png"
                alt="eggbug, smiling"
                {...attrs}
            />
        ),
        frowning: (
            <img
                class="eggbug"
                src="https://static.pyrope.net/eggbug-sad.png"
                alt="eggbug, frowning"
                {...attrs}
            />
        ),
        revengeance: (
            <img
                class="eggbug"
                src="https://static.pyrope.net/eggbug-revengeance.png"
                alt="ULTRA EGGBUG REVENGEANCE, MEGA. THERE IS FIRE. THERE IS TRISCAR. EGGBUG REVENGEANCE. YOU ARE. DIE!"
                {...attrs}
            />
        ),
        jpeg_annihilation: (
            <img
                class="eggbug"
                src="https://static.pyrope.net/eggbug-jpeg-annihilation.gif"
                alt="eggbug dissolving as the image gets more and more jpeg"
                {...attrs}
            />
        ),
        golfball: (
            <img
                class="eggbug"
                src="https://static.pyrope.net/eggbug-golfball.png"
                alt="eggbug as a golfball. no legs"
                {...attrs}
            />
        ),
    } as const);

export type EggbugEmotion = keyof ReturnType<typeof eggbug_emotions>;
type bla = ({
    type: EggbugEmotion;
} & JSX.HTMLAttributes<HTMLImageElement>)["type"];

export const EggbugImg = ({
    type,
    ...attrs
}: { type: EggbugEmotion } & Omit<
    JSX.HTMLAttributes<HTMLImageElement>,
    "type"
>) => eggbug_emotions(attrs)[type];

export const render_and_copy = (elem: VNode, pretty = false) => {
    const rendered = render(elem, null, { pretty });
    writeText(rendered);
    console.log(rendered);
};

export const mk_class_wrapper =
    (klass: string) =>
    ({ children }: { children?: ComponentChildren }) =>
        <div class={klass}>{...toChildArray(children)}</div>;

export const slidify = (
    Slide: (_: {
        slide: JSX.Element;
        next?: JSX.Element;
        n: number;
        max: number;
    }) => JSX.Element,
    slides: JSX.Element[],
    n = 1,
    max = slides.length
): JSX.Element =>
    slides[0] ? (
        <Slide
            slide={slides[0]}
            next={slidify(Slide, slides.slice(1), n + 1, max)}
            n={n}
            max={max}
        />
    ) : (
        <></>
    );

// https://cohost.org/lexyeevee/post/2107474-css-for-css-baby-3 (wayyy down at the bottom)
export const DisappearOnClick = ({
    children,
    className,
}: {
    className?: string;
    children: ComponentChildren;
}) => (
    <details
        open
        class={`disappearing ${className}`}
        style="position: relative; cursor: pointer;"
    >
        <summary style="list-style: none; position: absolute; inset: 0;"></summary>
        {children}
    </details>
);

/// make sure the margins are all legit
export const ToggleOnClick = ({
    children: [a, b],
}: {
    children: [ComponentChild, ComponentChild];
}) => (
    <details style="position: relative; margin: 0 auto; cursor: pointer;">
        <summary style="list-style: none;">{a}</summary>
        <div style="display: block; position: absolute; top: 0; pointer-events: none;">
            {b}
        </div>
    </details>
);

// TODO: probably doesn't work
export const ReduceOnClick = ({ children }: { children: VNode<{}>[] }) => {
    if (children.length == 0) return <></>;
    if (children.length == 1) return children[1];
    return (
        <details style="position: relative; margin: 0 auto; cursor: pointer;">
            <summary style="list-style: none;">{children[0]}</summary>
            <ReduceOnClick>{...children.slice(1)}</ReduceOnClick>
        </details>
    );
};

export function shuffle<T>(array: T[]): T[] {
    let currentIndex = array.length,
        randomIndex;

    // While there remain elements to shuffle.
    while (currentIndex != 0) {
        // Pick a remaining element.
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex--;

        // And swap it with the current element.
        [array[currentIndex], array[randomIndex]] = [
            array[randomIndex],
            array[currentIndex],
        ];
    }

    return array;
}

// type NOf<N extends number, T, R extends any[] = []> = R["length"] extends N
//     ? R
//     : NOf<N, T, [T, ...R]>;

// export function n_of<N extends number, T, R extends any[]>(
//     n: N,
//     x: T
// ): NOf<N, T, R> {
//     return [];
// }

export function n_of<T>(n: number, x: T): T[] {
    return new Array(n).fill(x);
}

export const static_url = (res: string) => `https://static.pyrope.net/${res}`;

export const randirect = (...urls: string[]) =>
    `https://pyrope.net/randirect#${urls.join("::")}`;

export const serverside_randirect = (...urls: string[]) =>
    `https://pyrope.net/serverside-randirect?${urls.join("::")}`;

// could do some [T, ...T[]] shenanigans for totality but meh
// kinda bad name
// unhappy with the randomness so i'm going way overkill lol
export const pick_random = <T,>(xs: readonly T[]): T =>
    xs[randomInt(xs.length)];

export const jitter = (n: number) => n * (Math.random() - 0.5);

export const svg_url = (svg: string) => `data:image/svg+xml,${encodeURI(svg)}`;

// something higher-level might be worthwhile...
// could namespace; e.g. css.font.sans_serif
// could put various beziers
export const css = {
    url(href: string) {
        return `url('${href}')`;
    },

    px(n: number) {
        return `${n}px`;
    },
    em(n: number) {
        return `${n}em`;
    },
    rem(n: number) {
        return `${n}rem`;
    },

    deg(n: number) {
        return `${n}deg`;
    },
    rad(n: number) {
        return `${n}rad`;
    },
    turn(n: number) {
        return `${n}turn`;
    },
    // now i feel like this could be generalizable
    // but js metaprogramming SUCKS
    transform(...ts: string[]) {
        return ts.join(" ");
    },
    rotate(by: string) {
        return `rotate(${by})`;
    },
    translateX(by: string) {
        return `translateX(${by})`;
    },
    translateY(by: string) {
        return `translateY(${by})`;
    },
    translate(x: string, y: string) {
        return `translate(${x}, ${y})`;
    },

    fontstack(...fonts: string[]) {
        return fonts.join(", ");
    },
    // # ruby i wont you ..
    inline_block: "inline-block",
    block: "block",
    grid: "grid",
    flex: "flex",
    fit_content: "fit-content",
    min_content: "min-content",
    max_content: "max-content",
} as const;

export const Filler = ({ height }: { height: string }) => (
    <div style={`height:${height}`}></div>
);

/// nonfunctional, deno's toplevel await (:D) is generally the best workaround
export function make_sync_no_matter_the_cost<T>(promise: Promise<T>): T {
    let done = false;
    let out;

    promise.then(t => {
        out = t;
        done = true;
    });

    return out as T;
}

// INFINITE CREDIT TO @BLACKLE https://cohost.org/blackle/post/72096-h3-style-text-alig
export const Cycle = ({
    width_px,
    height_px,
    children,
    style,
    credit = true,
}: {
    width_px: number;
    height_px: number;
    children: [ComponentChild, ...ComponentChild[]];
    style?: Record<string, string>;
    credit?: boolean;
}) => (
    <div
        style={{
            width: `${width_px}px`,
            height: `${height_px}px`,
            overflow: "hidden",
            ...(credit
                ? {
                      filter: "url(https://mehbark.github.io/#INFINITE%20CREDIT%20TO%20@BLACKLE)",
                  }
                : {}),
            ...style,
        }}
    >
        <div
            style={{
                display: "inline-flex",
                height: `${height_px}px`,
                paddingRight: `${width_px}px`,
                position: "relative",
                cursor: "pointer",
            }}
        >
            {...children.map((c, i) => (
                <details>
                    <summary
                        style={{
                            position: "absolute",
                            top: "0px",
                            left: `calc(-${width_px * 100}% + ${
                                width_px * width_px + width_px * i
                            }px)`,
                            // width: `${width_px}px`,
                            width: `calc(${
                                width_px * (children.length - 1) * 2 +
                                width_px -
                                width_px * i * 2
                            }px)`,
                            height: `${height_px}px`,
                            listStyle: "none",
                            overflow: "hidden",
                        }}
                    >
                        {c}
                    </summary>
                    <div
                        style={{
                            width: `${
                                i == children.length - 1
                                    ? children.length - 1
                                    : 1
                            }px`,
                        }}
                    ></div>
                </details>
            ))}
        </div>
    </div>
);

// yet another banger from @blackle
// i don't think we need the row height stuff for maximum genericity?
export const DragResizableImage = ({
    url,
    top,
    left,
    width,
    height,
    indicator = true,
    indicator_opacity = 0.3,
}: {
    url: string;
    top: number;
    left: number;
    width: number;
    height: number;
    indicator?: boolean;
    indicator_opacity?: number;
}) => (
    <div style="position: absolute;top: 0px;bottom: 0px;left: 0px;direction: rtl;font-size: 0px;line-height: 0;pointer-events: none;white-space: nowrap;">
        <div style="overflow: visible;width: 1px;height: 1px;display: inline-block;direction: ltr;vertical-align: text-top;position: relative;top: -9px;left: -9px;">
            <div style="position: relative;display: grid;grid-template-rows: 1fr;grid-template-columns: 1fr;">
                <div
                    style={`pointer-events: none;background-image: url(${url});background-size: 100% 100%;background-position: center center;background-repeat: no-repeat;grid-row: 1;grid-column: 1;transform: translate(-50%, -50%);`}
                ></div>
                <div
                    style={`overflow: hidden; resize: both; width: ${width}px;height: ${height}px;min-width: 36px; min-height: 36px; top: 0px; left: 0px; pointer-events: auto; position: relative; grid-area: 1 / 1 / auto / auto; transform: translate(-50%, -50%); clip-path: polygon(calc(100% - 18px) calc(100% - 18px), calc(100% - 18px) 100%, 100% 100%, 100% calc(100% - 18px));`}
                ></div>
            </div>
        </div>
        <div
            style={`overflow: hidden; resize: both; direction: ltr; display: inline-block; width: ${
                left + 18 + width / 2
            }px;height: ${
                top + 18 + height / 2
            }px; position: relative; opacity: ${indicator_opacity}; background: ${
                indicator
                    ? 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAQAAAD8x0bcAAAATklEQVR4AY3OAQYAMQwF0d7/0q2KrzFGZZZVPPLXxlc/RHIjEyIMxBnWBBAFcNODOaSo8X7C3uU/hY3Q5Bwm6vBkCKTiOU3mWkYkI5KQA4BYG/M4zBNLAAAAAElFTkSuQmCC")'
                    : "none"
            } 100% 100% no-repeat; clip-path: polygon(calc(100% - 18px) calc(100% - 18px), calc(100% - 18px) 100%, 100% 100%, 100% calc(100% - 18px)); pointer-events: auto;`}
        ></div>
    </div>
);

// don't really love this; do not recommend
export const string_split_once = (
    str: string,
    on: string
): [string, string] | undefined => {
    const idx = str.indexOf(on);
    if (idx < 0) return;
    return [str.slice(0, idx), str.slice(idx + on.length)];
};

export const HomestuckSpan = ({
    children,
    color,
    style,
}: {
    children: ComponentChildren;
    color?: string;
    style?: JSX.CSSProperties;
    attributes?: JSX.HTMLAttributes;
}) => (
    <span
        style={{
            fontFamily: "'courier-std', courier, monospace",
            fontWeight: "bold",
            ...{ color },
        }}
    >
        {...toChildArray(children)}
    </span>
);
