'use client';

import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { getDefaultEndpoints, MOCK_DEFINITIONS } from '@/mocks/definitions';
import type { MockConfig, MockEndpoints } from '@/mocks/definitions';

type DefsRecord = Record<
    string,
    Record<string, Record<string, { label: string }>>
>;

// Widened, runtime-shaped view of MockConfig['endpoints']. This toolbar
// renders whatever paths/methods MOCK_DEFINITIONS happens to contain at
// runtime, so it can't use the literal per-path/method typing MockEndpoints
// provides. MockEndpoints is structurally assignable to this shape, so
// reading/writing through it needs no cast.
type EndpointsRecord = Record<string, Record<string, { behavior: string }>>;

// Same idea as EndpointsRecord, but for MOCK_DEFINITIONS.
const mockDefinitions: DefsRecord = MOCK_DEFINITIONS;

function makeDefaultConfig(): MockConfig {
    return {
        delayMs: 0,
        endpoints: getDefaultEndpoints(),
    };
}

function getBehavior(config: MockConfig, path: string, method: string): string {
    const endpoints: EndpointsRecord = config.endpoints;

    return endpoints[path]?.[method]?.behavior ?? 'success';
}

async function postConfig(payload: object) {
    await fetch('/api/mock', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
    });
}

function BehaviorSelect(props: {
    path: string;
    method: string;
    config: MockConfig;
    behaviors: Record<string, { label: string }>;
    onChange: (path: string, method: string, behavior: string) => void;
}) {
    return (
        <div className="flex items-center justify-between gap-1.5">
            <span className={`w-1/4 text-white`}>
                {props.method.toUpperCase()}
            </span>
            <select
                value={getBehavior(props.config, props.path, props.method)}
                onChange={event =>
                    props.onChange(props.path, props.method, event.target.value)
                }
                className="flex-1 rounded bg-transparent px-1 py-0.5 text-12 text-white"
            >
                {Object.entries(props.behaviors).map(([value, { label }]) => (
                    <option key={value} value={value}>
                        {label}
                    </option>
                ))}
            </select>
        </div>
    );
}

export function MockToolbar() {
    const router = useRouter();
    const [open, setOpen] = useState(false);
    const [config, setConfig] = useState<MockConfig>(makeDefaultConfig);

    useEffect(() => {
        fetch('/api/mock')
            .then(r => r.json())
            .then(setConfig);
    }, []);

    async function applyDelay(delayMs: number) {
        setConfig(prev => ({ ...prev, delayMs }));

        await postConfig({ delayMs });

        router.refresh();
    }

    async function applyEndpoint(
        path: string,
        method: string,
        behavior: string
    ) {
        const patch = { behavior };

        setConfig(prev => {
            const endpoints: EndpointsRecord = prev.endpoints;

            const nextEndpoints: EndpointsRecord = {
                ...endpoints,
                [path]: {
                    ...endpoints[path],
                    [method]: {
                        ...endpoints[path][method],
                        ...patch,
                    },
                },
            };

            // Writing dynamic path/method strings back into MockEndpoints'
            // literal-keyed shape can't be verified at compile time - this
            // is the one point where we have to assert it's still valid.
            return { ...prev, endpoints: nextEndpoints as MockEndpoints };
        });

        await postConfig({ endpoints: { [path]: { [method]: patch } } });

        router.refresh();
    }

    async function reset() {
        await postConfig({ reset: true });

        setConfig(makeDefaultConfig());

        router.refresh();
    }

    return (
        <div className="fixed right-0 bottom-0 z-50 flex max-h-screen flex-col justify-start p-4 font-mono text-12">
            <button
                onClick={() => setOpen(o => !o)}
                className="rounded bg-gray-900 px-3 py-1.5 text-white shadow-lg hover:bg-gray-700"
            >
                Mock server {open ? '-' : '+'}
            </button>

            {open && (
                <div className="scrollbar-none mt-1 flex w-80 flex-col gap-3 overflow-auto rounded bg-gray-900 p-3 text-white shadow-xl">
                    {Object.entries(mockDefinitions).map(([path, methods]) => (
                        <div className="flex flex-col gap-2" key={path}>
                            <div className="text-gray-400">
                                {path.replace(/\{([^}]+)\}/g, ':$1')}
                            </div>
                            <div className="flex flex-col gap-4">
                                {Object.entries(methods).map(
                                    ([method, behaviors]) => (
                                        <BehaviorSelect
                                            path={path}
                                            method={method}
                                            config={config}
                                            behaviors={behaviors}
                                            onChange={applyEndpoint}
                                            key={method}
                                        />
                                    )
                                )}
                            </div>
                        </div>
                    ))}

                    <hr className="border-gray-700" />

                    <div className="flex items-center gap-2">
                        <label className="flex flex-col gap-0.5">
                            <span className="text-gray-400">Delay (ms)</span>
                            <input
                                type="number"
                                min={0}
                                step={100}
                                value={config.delayMs}
                                onChange={event =>
                                    applyDelay(Number(event.target.value))
                                }
                                className="w-20 rounded bg-transparent px-2 py-1 text-white"
                            />
                        </label>

                        <button
                            onClick={reset}
                            className="mt-4 ml-auto rounded bg-red-700 px-2 py-1 hover:bg-red-600"
                        >
                            Reset
                        </button>
                    </div>
                </div>
            )}
        </div>
    );
}
